<?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>Cross-Module on Tarragon</title><link>https://tarrragon.github.io/blog/tags/cross-module/</link><description>Recent content in Cross-Module on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 22 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/cross-module/index.xml" rel="self" type="application/rss+xml"/><item><title>0.15 跨模組 Checkout Episode：從資料寫入到觀測證據</title><link>https://tarrragon.github.io/blog/backend/00-service-selection/cross-module-checkout-episode/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/00-service-selection/cross-module-checkout-episode/</guid><description>&lt;p>跨模組 checkout episode 的核心責任是用同一條服務路徑，把資料庫、快取、訊息佇列與可觀測性四個模組的責任串在一起。讀者看完後能判斷一次 checkout 請求觸發的狀態寫入、快取失效、事件發布與訊號記錄分別由誰負責，以及任何一層失敗時該看哪組訊號。&lt;/p>
&lt;p>本篇與 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/operations-control-vertical-slice/" data-link-title="0.13 操作控制 vertical slice 實作入口" data-link-desc="用一個服務串起觀測證據、可靠性驗證、事故決策與回寫閉環">0.13 操作控制 vertical slice&lt;/a> 互補：0.13 走的是 04/06/08 的操作控制閉環（觀測 → 驗證 → 事故 → 回寫），本篇走的是 01/02/03/04 的資料基礎設施鏈（狀態 → 副本 → 事件 → 訊號）。&lt;/p>
&lt;h2 id="服務路徑">服務路徑&lt;/h2>
&lt;p>一次 checkout 的最小路徑：&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">client
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> → checkout-api
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> → order-db (01: 寫入正式狀態)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> → cache invalidation (02: 失效商品快取)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> → event publish (03: 發布 order.created 事件)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> → telemetry (04: span / log / metric 記錄)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這條路徑刻意簡化。真實系統可能還有 payment adapter、inventory lock、notification service、search index sync 等環節，但四層串聯的責任分工用最小路徑就能說明。後續章節把各層展開。&lt;/p>
&lt;h2 id="第一層資料庫寫入01">第一層：資料庫寫入（01）&lt;/h2>
&lt;p>Checkout 的正式狀態是訂單紀錄。這筆寫入必須在 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">transaction boundary&lt;/a> 內完成，確保訂單、明細與付款紀錄一起成功或一起失敗。&lt;/p>
&lt;p>&lt;strong>責任邊界&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>訂單狀態是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">source of truth&lt;/a>，快取和事件都是下游副本&lt;/li>
&lt;li>Transaction 範圍盡量小：寫入訂單 + 明細 + outbox record，不在同一個 transaction 裡做外部 API 呼叫&lt;/li>
&lt;li>Schema 需要支援狀態演進：訂單從 &lt;code>pending&lt;/code> → &lt;code>paid&lt;/code> → &lt;code>shipped&lt;/code> 的欄位設計見 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 schema migration rollout evidence&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>失敗判讀&lt;/strong>：&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>Transaction timeout&lt;/td>
 &lt;td>連線池飽和或長 transaction 鎖等待&lt;/td>
 &lt;td>回 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1 高併發讀寫邊界&lt;/a> 檢查連線池與 transaction 範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Deadlock&lt;/td>
 &lt;td>多個 checkout 同時更新重疊資源&lt;/td>
 &lt;td>回 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">1.3 transaction boundary&lt;/a> 檢查 lock ordering&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Schema migration 中斷&lt;/td>
 &lt;td>欄位變更與正在執行的寫入衝突&lt;/td>
 &lt;td>回 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6 migration playbook&lt;/a> 確認 expand/contract 流程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>交接給下一層的資訊&lt;/strong>：transaction commit 成功後，訂單 ID 與狀態就緒。Outbox record 已寫入同一個 transaction。&lt;/p></description><content:encoded><![CDATA[<p>跨模組 checkout episode 的核心責任是用同一條服務路徑，把資料庫、快取、訊息佇列與可觀測性四個模組的責任串在一起。讀者看完後能判斷一次 checkout 請求觸發的狀態寫入、快取失效、事件發布與訊號記錄分別由誰負責，以及任何一層失敗時該看哪組訊號。</p>
<p>本篇與 <a href="/blog/backend/00-service-selection/operations-control-vertical-slice/" data-link-title="0.13 操作控制 vertical slice 實作入口" data-link-desc="用一個服務串起觀測證據、可靠性驗證、事故決策與回寫閉環">0.13 操作控制 vertical slice</a> 互補：0.13 走的是 04/06/08 的操作控制閉環（觀測 → 驗證 → 事故 → 回寫），本篇走的是 01/02/03/04 的資料基礎設施鏈（狀態 → 副本 → 事件 → 訊號）。</p>
<h2 id="服務路徑">服務路徑</h2>
<p>一次 checkout 的最小路徑：</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">client
</span></span><span class="line"><span class="ln">2</span><span class="cl">  → checkout-api
</span></span><span class="line"><span class="ln">3</span><span class="cl">    → order-db          (01: 寫入正式狀態)
</span></span><span class="line"><span class="ln">4</span><span class="cl">    → cache invalidation (02: 失效商品快取)
</span></span><span class="line"><span class="ln">5</span><span class="cl">    → event publish      (03: 發布 order.created 事件)
</span></span><span class="line"><span class="ln">6</span><span class="cl">    → telemetry          (04: span / log / metric 記錄)</span></span></code></pre></div><p>這條路徑刻意簡化。真實系統可能還有 payment adapter、inventory lock、notification service、search index sync 等環節，但四層串聯的責任分工用最小路徑就能說明。後續章節把各層展開。</p>
<h2 id="第一層資料庫寫入01">第一層：資料庫寫入（01）</h2>
<p>Checkout 的正式狀態是訂單紀錄。這筆寫入必須在 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">transaction boundary</a> 內完成，確保訂單、明細與付款紀錄一起成功或一起失敗。</p>
<p><strong>責任邊界</strong>：</p>
<ul>
<li>訂單狀態是 <a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">source of truth</a>，快取和事件都是下游副本</li>
<li>Transaction 範圍盡量小：寫入訂單 + 明細 + outbox record，不在同一個 transaction 裡做外部 API 呼叫</li>
<li>Schema 需要支援狀態演進：訂單從 <code>pending</code> → <code>paid</code> → <code>shipped</code> 的欄位設計見 <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 evidence</a></li>
</ul>
<p><strong>失敗判讀</strong>：</p>
<table>
  <thead>
      <tr>
          <th>失敗訊號</th>
          <th>判讀</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Transaction timeout</td>
          <td>連線池飽和或長 transaction 鎖等待</td>
          <td>回 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1 高併發讀寫邊界</a> 檢查連線池與 transaction 範圍</td>
      </tr>
      <tr>
          <td>Deadlock</td>
          <td>多個 checkout 同時更新重疊資源</td>
          <td>回 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">1.3 transaction boundary</a> 檢查 lock ordering</td>
      </tr>
      <tr>
          <td>Schema migration 中斷</td>
          <td>欄位變更與正在執行的寫入衝突</td>
          <td>回 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6 migration playbook</a> 確認 expand/contract 流程</td>
      </tr>
  </tbody>
</table>
<p><strong>交接給下一層的資訊</strong>：transaction commit 成功後，訂單 ID 與狀態就緒。Outbox record 已寫入同一個 transaction。</p>
<h2 id="第二層快取失效02">第二層：快取失效（02）</h2>
<p>訂單成功後，商品庫存或價格的快取副本可能已經過期。快取失效的責任是讓後續讀取拿到正確狀態，同時保護資料庫不被回源壓力打穿。</p>
<p><strong>責任邊界</strong>：</p>
<ul>
<li>快取是 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">可重建副本</a>，資料來源是資料庫的正式狀態。失效後的 cache miss 會回源到資料庫</li>
<li>失效策略用 <a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">cache aside</a>：寫入後主動 invalidate，下次讀取時 lazy reload</li>
<li>Invalidation 的順序：先 invalidate 應用層快取（Redis），再考慮是否需要 purge CDN 層（若商品頁有 edge cache）</li>
</ul>
<p><strong>失敗判讀</strong>：</p>
<table>
  <thead>
      <tr>
          <th>失敗訊號</th>
          <th>判讀</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Invalidation 失敗但 DB 已 commit</td>
          <td>快取短暫提供舊資料，freshness window 內自動修正</td>
          <td>確認 <a href="/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">TTL</a> 是否足夠短，或補 retry</td>
      </tr>
      <tr>
          <td>Cache stampede</td>
          <td>大量 invalidation 同時觸發 origin 回源</td>
          <td>回 <a href="/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">2.9 cache migration stampede rollback</a> 補 singleflight 或 lock</td>
      </tr>
      <tr>
          <td>Hot key 集中失效</td>
          <td>單一商品被大量並發 checkout 同時 invalidate</td>
          <td>回 <a href="/blog/backend/02-cache-redis/high-concurrency-access/" data-link-title="2.1 高併發下的 Redis 讀寫邊界" data-link-desc="說明高併發服務如何共用 Redis client、控制 pipeline 與避免 cache stampede">2.1 高併發讀寫邊界</a> 檢查 hot key 分散策略</td>
      </tr>
  </tbody>
</table>
<p><strong>交接給下一層的資訊</strong>：快取失效完成（或 TTL 保底）。接下來的事件發布不依賴快取狀態 — 事件內容來自 DB 寫入結果。</p>
<h2 id="第三層事件發布03">第三層：事件發布（03）</h2>
<p>訂單寫入後，<code>order.created</code> 事件需要傳遞到下游：通知服務寄信、庫存服務更新、搜尋索引同步、分析管道記錄。這些下游不在 checkout request 內完成，要用非同步傳遞。</p>
<p><strong>責任邊界</strong>：</p>
<ul>
<li>事件發布與 DB 寫入的一致性用 <a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">outbox pattern</a>：outbox record 在 DB transaction 內寫入，poller 或 CDC 負責把 record 發到 broker</li>
<li>Broker 保證 <a href="/blog/backend/knowledge-cards/delivery-semantics/" data-link-title="Delivery Semantics" data-link-desc="說明事件投遞語意如何定義遺失、重複、順序與補償策略">at-least-once delivery</a>，consumer 需要做 <a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> 處理</li>
<li>Event contract（schema、idempotency key、replay window）見 <a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract replay boundary</a></li>
</ul>
<p><strong>失敗判讀</strong>：</p>
<table>
  <thead>
      <tr>
          <th>失敗訊號</th>
          <th>判讀</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Outbox poller 延遲</td>
          <td>事件延遲但不遺失，DB 已 commit</td>
          <td>監控 outbox table 的 pending row count，回 <a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern</a></td>
      </tr>
      <tr>
          <td>Consumer lag 上升</td>
          <td>下游處理速度跟不上，事件在 broker 堆積</td>
          <td>回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a> 檢查 consumer 數量與 backpressure</td>
      </tr>
      <tr>
          <td>DLQ 堆積</td>
          <td>毒訊息或下游持續失敗，已超過 retry 預算</td>
          <td>回 <a href="/blog/backend/03-message-queue/queue-consumer-retry-replay-handoff/" data-link-title="3.8 Queue Consumer Retry 與 Replay Handoff（實作示範）" data-link-desc="以 order_created consumer 示範 queue 路徑如何交付 idempotency evidence、DLQ handling、replay runbook 與 incident decision log。">3.8 retry replay handoff</a> 啟動 DLQ drain runbook</td>
      </tr>
      <tr>
          <td>重複事件造成下游重複副作用</td>
          <td>Consumer idempotency 沒擋住</td>
          <td>回 <a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a> 確認去重機制</td>
      </tr>
  </tbody>
</table>
<p><strong>交接給下一層的資訊</strong>：事件已發到 broker，每一步（publish、ack、consume、DLQ）都需要觀測訊號。</p>
<h2 id="第四層觀測訊號04">第四層：觀測訊號（04）</h2>
<p>以上三層的每一步都需要被記錄成可查詢的訊號。Checkout 路徑的觀測責任是讓事故判讀者能用同一組 trace ID 串起完整鏈路。</p>
<p><strong>責任邊界</strong>：</p>
<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">Trace context</a> 從 client 一路 propagate 到 consumer，跨 sync（HTTP）與 async（queue）邊界</li>
<li><a href="/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">Log schema</a> 使用統一欄位：<code>order_id</code>、<code>trace_id</code>、<code>tenant_id</code>、<code>region</code></li>
<li><a href="/blog/backend/04-observability/metrics-basics/" data-link-title="4.2 metrics 與 SLI/SLO" data-link-desc="整理 counter、gauge、histogram 與服務健康指標">Metrics</a> 覆蓋三組 SLI：checkout latency（p50/p95/p99）、checkout error rate、event publish lag</li>
<li><a href="/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">Dashboard</a> 把上述三組 SLI 放在同一個 checkout 服務面板</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。">Evidence package</a> 把查詢、時間窗、資料品質與 owner 打包成可交接證據</li>
</ul>
<p><strong>失敗判讀</strong>：</p>
<table>
  <thead>
      <tr>
          <th>失敗訊號</th>
          <th>判讀</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Trace 在 DB commit 後斷鏈</td>
          <td>Context propagation 沒跨到 async 邊界</td>
          <td>回 <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> 補 queue span link</td>
      </tr>
      <tr>
          <td>Checkout metric 正常但客訴增加</td>
          <td>觀測盲區或 sampling 偏差</td>
          <td>回 <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> 標示 known gap</td>
      </tr>
      <tr>
          <td>Alert 太吵但真正事件沒被抓到</td>
          <td>告警粒度與閾值設計問題</td>
          <td>回 <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> 調整 symptom-based alert</td>
      </tr>
      <tr>
          <td>訊號延遲導致事故判讀困難</td>
          <td>Pipeline ingest delay 或 metric scrape interval 太長</td>
          <td>回 <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 健康</td>
      </tr>
  </tbody>
</table>
<h2 id="四層交接總覽">四層交接總覽</h2>





<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">┌─────────────┐    commit     ┌──────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│  01 DB      │──────────────→│  02 Cache    │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│  order-db   │    ok         │  invalidate  │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│  write      │               │  product key │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">└──────┬──────┘               └──────────────┘
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">       │ outbox
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">       │ record
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">       ▼
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">┌─────────────┐
</span></span><span class="line"><span class="ln">10</span><span class="cl">│  03 Event   │
</span></span><span class="line"><span class="ln">11</span><span class="cl">│  publish    │
</span></span><span class="line"><span class="ln">12</span><span class="cl">│  order.     │
</span></span><span class="line"><span class="ln">13</span><span class="cl">│  created    │
</span></span><span class="line"><span class="ln">14</span><span class="cl">└─────────────┘
</span></span><span class="line"><span class="ln">15</span><span class="cl">       │
</span></span><span class="line"><span class="ln">16</span><span class="cl">       │ all layers emit
</span></span><span class="line"><span class="ln">17</span><span class="cl">       ▼
</span></span><span class="line"><span class="ln">18</span><span class="cl">┌──────────────────────────┐
</span></span><span class="line"><span class="ln">19</span><span class="cl">│  04 Observability        │
</span></span><span class="line"><span class="ln">20</span><span class="cl">│  span + log + metric     │
</span></span><span class="line"><span class="ln">21</span><span class="cl">│  per layer               │
</span></span><span class="line"><span class="ln">22</span><span class="cl">└──────────────────────────┘</span></span></code></pre></div><p>每一層都有明確的失敗判讀與交接資訊。四層合在一起的判讀順序是：先看 04 的 trace 確認斷點在哪一層，再進那一層的失敗訊號表。</p>
<h2 id="跨層失敗場景">跨層失敗場景</h2>
<p>單層失敗表只處理各自的責任。跨層失敗需要同時看多組訊號：</p>
<h3 id="db-commit-成功但快取沒失效且事件沒發出">DB commit 成功，但快取沒失效且事件沒發出</h3>
<p>原因通常是 outbox poller 和 cache invalidation 在同一個 request 內串行、前者失敗後沒做到後者。判讀順序：</p>
<ol>
<li>04 的 trace 看 checkout span 是否有 error tag</li>
<li>01 的 outbox table 看 pending row 是否堆積</li>
<li>02 的 cache key 是否仍是舊值（TTL 保底正常時可接受）</li>
</ol>
<p>修正方向：invalidation 和 outbox 解耦 — invalidation 在 DB commit 後同步執行（失敗可 retry），outbox 非同步由 poller 負責。兩者不應互相阻塞。</p>
<h3 id="event-consumer-重複處理造成庫存扣兩次">Event consumer 重複處理造成庫存扣兩次</h3>
<p>原因是 consumer 的 idempotency 沒做好，broker redelivery 導致重複副作用。判讀順序：</p>
<ol>
<li>04 的 consumer span 看 redelivery count</li>
<li>03 的 DLQ 看是否有 poison message</li>
<li>01 的 inventory table 看同一 order_id 是否有多筆扣減</li>
</ol>
<p>修正方向：回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a> 補 idempotency key 驗證，用 order_id 當去重鍵。</p>
<h3 id="checkout-latency-上升但-db-和-cache-都正常">Checkout latency 上升但 DB 和 cache 都正常</h3>
<p>原因可能是 outbox poller 或 event publish 在 request path 內同步等待（設計錯誤）。判讀順序：</p>
<ol>
<li>04 的 checkout span 看 child span 時間分布</li>
<li>確認 event publish 是否在 request 返回前完成（不該）</li>
<li>如果是，回到 03 確認 outbox pattern 是否正確實作（寫 outbox record 應在 DB transaction 內、publish 應由 poller 異步執行）</li>
</ol>
<h2 id="各模組回讀路由">各模組回讀路由</h2>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>主要回讀章節</th>
          <th>回讀時機</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>01 DB</td>
          <td><a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1</a>、<a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">1.3</a>、<a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6</a>、<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</a></td>
          <td>transaction 或 schema 問題</td>
      </tr>
      <tr>
          <td>02 Cache</td>
          <td><a href="/blog/backend/02-cache-redis/high-concurrency-access/" data-link-title="2.1 高併發下的 Redis 讀寫邊界" data-link-desc="說明高併發服務如何共用 Redis client、控制 pipeline 與避免 cache stampede">2.1</a>、<a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2</a>、<a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">2.7</a>、<a href="/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">2.9</a></td>
          <td>invalidation 或 stampede 問題</td>
      </tr>
      <tr>
          <td>03 Event</td>
          <td><a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3</a>、<a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4</a>、<a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6</a>、<a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7</a></td>
          <td>delivery、idempotency 或 replay 問題</td>
      </tr>
      <tr>
          <td>04 Observability</td>
          <td><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</a>、<a href="/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">4.4</a>、<a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17</a>、<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</a></td>
          <td>訊號斷鏈、盲區或 evidence 問題</td>
      </tr>
      <tr>
          <td>操作閉環</td>
          <td><a href="/blog/backend/00-service-selection/operations-control-vertical-slice/" data-link-title="0.13 操作控制 vertical slice 實作入口" data-link-desc="用一個服務串起觀測證據、可靠性驗證、事故決策與回寫閉環">0.13</a></td>
          <td>從訊號進入驗證、事故與回寫流程</td>
      </tr>
  </tbody>
</table>
<h2 id="使用方式">使用方式</h2>
<p>本篇是索引型讀物。讀者第一次讀時順著四層走一遍，建立跨模組的交接心智模型。之後遇到具體問題時，用失敗訊號表定位到對應模組的章節。</p>
<p>已經有某一層經驗的讀者可以從那一層開始讀，看該層與相鄰層的交接欄位是否對齊。資料庫工程師從第一層開始看事件發布的交接；觀測工程師從第四層反推前三層需要哪些欄位。</p>
<p>本篇不處理 payment adapter、inventory lock、notification 等更複雜的分支。這些分支的模式相同 — 確認責任邊界、交接欄位與失敗判讀 — 讀者可以自行延伸。</p>
]]></content:encoded></item><item><title>監控資料的雙重用途：行為分析與訊號治理</title><link>https://tarrragon.github.io/blog/monitoring/telemetry-data-dual-use/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/telemetry-data-dual-use/</guid><description>&lt;p>SDK 埋的每一筆 event 有兩個下游消費者：產品團隊用它做行為分析（轉換率、留存、歸因），工程團隊用它做訊號治理（cardinality 控制、成本歸因、事故判讀）。兩邊各自有教學章節（&lt;a href="https://tarrragon.github.io/blog/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">Monitoring 08 Business Analytics&lt;/a> 和 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">Backend 04 可觀測性&lt;/a>），但讀者常不知道這是同一份資料的兩種消費方式。本文是橋。&lt;/p>
&lt;h2 id="同一份資料兩種消費路徑">同一份資料、兩種消費路徑&lt;/h2>





&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">SDK 埋點（event / error / metric / lifecycle）
&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"> ├── 行為分析路徑 → Monitoring 08
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> │ 消費者：PM / 行銷 / 產品
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> │ 方法：funnel / cohort / attribution / A-B test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> │ 決策：改 UI、調定價、投廣告
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> └── 訊號治理路徑 → Backend 04
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> 消費者：SRE / platform team / on-call
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> 方法：cardinality budget / cost attribution / signal governance
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> 決策：降 cardinality、調 sampling、改 alert、產出 evidence&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這不是兩套埋點。同一個 &lt;code>button.click&lt;/code> event，產品團隊看的是「哪個步驟流失最多使用者」，工程團隊看的是「這個 event 的 cardinality 是否在預算內、ingestion cost 是否合理」。event 相同，切入角度不同。&lt;/p>
&lt;h2 id="資料格式的交叉點">資料格式的交叉點&lt;/h2>
&lt;p>Monitoring SDK 送出的事件格式（&lt;a href="https://tarrragon.github.io/blog/monitoring/02-log-schema/" data-link-title="模組二：Log Schema 設計" data-link-desc="跨平台統一事件格式、欄位設計、版本演進策略">02 Log Schema&lt;/a>）和 Backend 04 的 log schema / OTel event format 有共通欄位：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>欄位&lt;/th>
 &lt;th>Monitoring SDK 格式&lt;/th>
 &lt;th>Backend 04 / OTel 格式&lt;/th>
 &lt;th>交叉用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>timestamp&lt;/td>
 &lt;td>&lt;code>timestamp&lt;/code>（ISO 8601）&lt;/td>
 &lt;td>&lt;code>TimeUnixNano&lt;/code>&lt;/td>
 &lt;td>兩邊都需要精確時間做時序查詢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>event type&lt;/td>
 &lt;td>&lt;code>type&lt;/code>（event/error/metric/lifecycle）&lt;/td>
 &lt;td>&lt;code>SeverityText&lt;/code> / &lt;code>SpanKind&lt;/code>&lt;/td>
 &lt;td>行為分析按 type 做 funnel；訊號治理按 type 做 cardinality budget&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>source&lt;/td>
 &lt;td>&lt;code>source.sdk&lt;/code> / &lt;code>source.platform&lt;/code> / &lt;code>source.app&lt;/code>&lt;/td>
 &lt;td>&lt;code>Resource&lt;/code> attributes&lt;/td>
 &lt;td>行為分析按 platform 切分；訊號治理按 service 做 cost attribution&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>trace context&lt;/td>
 &lt;td>手動注入（若有）&lt;/td>
 &lt;td>&lt;code>TraceId&lt;/code> / &lt;code>SpanId&lt;/code>&lt;/td>
 &lt;td>client-to-server 端到端追蹤的串接欄位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>payload&lt;/td>
 &lt;td>&lt;code>data&lt;/code>（自由 JSON）&lt;/td>
 &lt;td>&lt;code>Attributes&lt;/code> / &lt;code>Body&lt;/code>&lt;/td>
 &lt;td>行為分析讀 business fields；訊號治理讀 operational fields&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>格式一致性的價值是&lt;strong>一份 event 同時餵 BigQuery（行為分析）和 Grafana Loki（訊號查詢）不需要格式轉換&lt;/strong>。如果兩邊各自定義 schema，同一個 event 要寫兩次 adapter，schema drift 的風險倍增。&lt;/p></description><content:encoded><![CDATA[<p>SDK 埋的每一筆 event 有兩個下游消費者：產品團隊用它做行為分析（轉換率、留存、歸因），工程團隊用它做訊號治理（cardinality 控制、成本歸因、事故判讀）。兩邊各自有教學章節（<a href="/blog/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">Monitoring 08 Business Analytics</a> 和 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">Backend 04 可觀測性</a>），但讀者常不知道這是同一份資料的兩種消費方式。本文是橋。</p>
<h2 id="同一份資料兩種消費路徑">同一份資料、兩種消費路徑</h2>





<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">SDK 埋點（event / error / metric / lifecycle）
</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">  ├── 行為分析路徑 → Monitoring 08
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  │     消費者：PM / 行銷 / 產品
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │     方法：funnel / cohort / attribution / A-B test
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │     決策：改 UI、調定價、投廣告
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └── 訊號治理路徑 → Backend 04
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        消費者：SRE / platform team / on-call
</span></span><span class="line"><span class="ln">10</span><span class="cl">        方法：cardinality budget / cost attribution / signal governance
</span></span><span class="line"><span class="ln">11</span><span class="cl">        決策：降 cardinality、調 sampling、改 alert、產出 evidence</span></span></code></pre></div><p>這不是兩套埋點。同一個 <code>button.click</code> event，產品團隊看的是「哪個步驟流失最多使用者」，工程團隊看的是「這個 event 的 cardinality 是否在預算內、ingestion cost 是否合理」。event 相同，切入角度不同。</p>
<h2 id="資料格式的交叉點">資料格式的交叉點</h2>
<p>Monitoring SDK 送出的事件格式（<a href="/blog/monitoring/02-log-schema/" data-link-title="模組二：Log Schema 設計" data-link-desc="跨平台統一事件格式、欄位設計、版本演進策略">02 Log Schema</a>）和 Backend 04 的 log schema / OTel event format 有共通欄位：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Monitoring SDK 格式</th>
          <th>Backend 04 / OTel 格式</th>
          <th>交叉用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>timestamp</td>
          <td><code>timestamp</code>（ISO 8601）</td>
          <td><code>TimeUnixNano</code></td>
          <td>兩邊都需要精確時間做時序查詢</td>
      </tr>
      <tr>
          <td>event type</td>
          <td><code>type</code>（event/error/metric/lifecycle）</td>
          <td><code>SeverityText</code> / <code>SpanKind</code></td>
          <td>行為分析按 type 做 funnel；訊號治理按 type 做 cardinality budget</td>
      </tr>
      <tr>
          <td>source</td>
          <td><code>source.sdk</code> / <code>source.platform</code> / <code>source.app</code></td>
          <td><code>Resource</code> attributes</td>
          <td>行為分析按 platform 切分；訊號治理按 service 做 cost attribution</td>
      </tr>
      <tr>
          <td>trace context</td>
          <td>手動注入（若有）</td>
          <td><code>TraceId</code> / <code>SpanId</code></td>
          <td>client-to-server 端到端追蹤的串接欄位</td>
      </tr>
      <tr>
          <td>payload</td>
          <td><code>data</code>（自由 JSON）</td>
          <td><code>Attributes</code> / <code>Body</code></td>
          <td>行為分析讀 business fields；訊號治理讀 operational fields</td>
      </tr>
  </tbody>
</table>
<p>格式一致性的價值是<strong>一份 event 同時餵 BigQuery（行為分析）和 Grafana Loki（訊號查詢）不需要格式轉換</strong>。如果兩邊各自定義 schema，同一個 event 要寫兩次 adapter，schema drift 的風險倍增。</p>
<h2 id="資料治理的衝突">資料治理的衝突</h2>
<p>同一份資料被兩邊消費時，治理需求會衝突：</p>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>行為分析需要</th>
          <th>訊號治理需要</th>
          <th>衝突點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>保留期</td>
          <td>長期保留（年級，趨勢與 cohort 需要歷史資料）</td>
          <td>短期保留（30-90 天，debug 用完即丟）</td>
          <td>成本 vs 分析完整度</td>
      </tr>
      <tr>
          <td>粒度</td>
          <td>高粒度（per-user、per-session、per-action）</td>
          <td>低粒度（聚合到 service / endpoint 維度）</td>
          <td>cardinality 爆炸 vs 分析精度</td>
      </tr>
      <tr>
          <td>PII 處理</td>
          <td>去識別但需保留 user segment（國家、裝置、方案）</td>
          <td>完全匿名或 redacted</td>
          <td>分析需求 vs 合規要求</td>
      </tr>
      <tr>
          <td>取樣</td>
          <td>低取樣或全量（行為趨勢需要完整分布）</td>
          <td>可以高取樣（error 全收，正常 request 取樣即可）</td>
          <td>成本 vs 覆蓋度</td>
      </tr>
      <tr>
          <td>查詢延遲</td>
          <td>可接受分鐘級（batch analytics）</td>
          <td>需要秒級（incident debug 不能等）</td>
          <td>儲存分層與查詢 backend 選擇</td>
      </tr>
  </tbody>
</table>
<p>這些衝突無法靠「選一邊」解決。行為分析少了歷史資料就看不到趨勢；訊號治理存太多高粒度資料就 cardinality 爆炸。解法是分流。</p>
<h2 id="解法在-transport-層分流">解法：在 transport 層分流</h2>
<p>把 SDK 送出的 event 在 collector 或 pipeline 層分流到不同 backend，各自按需求治理：</p>
<h3 id="hot-path即時訊號">Hot path：即時訊號</h3>
<p>error 和 metric 類事件即時進入 <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 分層治理">04 telemetry pipeline</a>（Loki / Prometheus / Tempo），短期 retention（30-90 天），服務 on-call debug 和 incident triage。這條路徑要求秒級延遲、低 cardinality（聚合維度）。</p>
<h3 id="warm-path行為分析">Warm path：行為分析</h3>
<p>全部四類事件進入 data warehouse（BigQuery / ClickHouse / Snowflake），長期 retention（年級），服務 funnel、cohort、attribution 和 A/B test。這條路徑接受分鐘級延遲、高粒度（per-user / per-session）。</p>
<h3 id="cold-path合規留存">Cold path：合規留存</h3>
<p>audit-level event 進入 archive storage（Cloud Storage / S3 / Glacier），法規要求的年級保留（GDPR 刪除請求、HIPAA 6 年、金融業更長）。這條路徑寫入後幾乎不查詢，查詢時接受小時級延遲。</p>
<h3 id="分流的關鍵設計">分流的關鍵設計</h3>
<p>分流在 transport 層做，不在 SDK 層做。SDK 統一送出全部 event 到同一個 endpoint，pipeline 按 event type / source / tag 路由到不同 backend。</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">SDK → Collector / OTel Collector / Cloud Logging
</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">         ├─ [type=error OR type=metric] → Hot path (Loki / Prometheus)
</span></span><span class="line"><span class="ln">4</span><span class="cl">         ├─ [all events]                → Warm path (BigQuery)
</span></span><span class="line"><span class="ln">5</span><span class="cl">         └─ [audit=true]               → Cold path (Cloud Storage)</span></span></code></pre></div><p>SDK 不需要知道下游有幾個消費者。新增一個消費者（例如新的分析平台）只要在 pipeline 加一條路由，不用改 SDK。</p>
<h2 id="實作考量">實作考量</h2>
<p>分流的實作方式取決於 pipeline 架構：</p>
<table>
  <thead>
      <tr>
          <th>架構</th>
          <th>分流機制</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自架 collector（<a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">Monitoring 04</a>）</td>
          <td>Rule engine 按 event type 寫不同 output file / HTTP endpoint</td>
          <td>小規模、自用場景</td>
      </tr>
      <tr>
          <td>OTel Collector</td>
          <td>Processor + 多個 Exporter 組成 pipeline fan-out</td>
          <td>中規模、已採用 OTel</td>
      </tr>
      <tr>
          <td>Cloud Logging（GCP）</td>
          <td>Subscription filter + Sink（BigQuery / Cloud Storage / Pub/Sub）</td>
          <td>GCP 生態</td>
      </tr>
      <tr>
          <td>Kinesis / Firehose（AWS）</td>
          <td>Firehose delivery stream + Lambda transform</td>
          <td>AWS 生態</td>
      </tr>
  </tbody>
</table>
<p>不論哪種架構，分流後的每條 path 要各自設定 retention、sampling、PII handling 和 cost budget。Hot path 的 <a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">cardinality 治理</a> 規則不該影響 warm path 的分析粒度；warm path 的長期保留成本不該擠壓 hot path 的 freshness。</p>
<h2 id="常見誤區">常見誤區</h2>
<h3 id="用兩套-sdk-替代分流">用兩套 SDK 替代分流</h3>
<p>在 client 端同時整合行為分析 SDK（Mixpanel）和 error tracking SDK（Sentry），看似分工清楚，實際是兩套 schema、兩份 ingestion cost、兩組 PII 風險面、兩套 consent 管理。同一個 user action 在兩個平台各記一次，但欄位名、timestamp 精度、user identifier 可能不同，跨平台 correlation 困難。</p>
<p>統一 SDK + pipeline 分流的成本通常低於雙 SDK 的整合與治理成本。</p>
<h3 id="hot-path-存全量高粒度">Hot path 存全量高粒度</h3>
<p>把 per-user / per-session 的完整事件直接灌進 Prometheus 或 Loki，會導致 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 Cardinality 治理</a>）。Hot path 的正確做法是在 pipeline 層做 aggregation 或 relabeling，只保留 service / endpoint / status 等低 cardinality 維度。高粒度資料走 warm path。</p>
<h3 id="warm-path-不做-pii-處理">Warm path 不做 PII 處理</h3>
<p>行為分析需要 user segment，但不需要 PII 原文。warm path 的 ingestion pipeline 應該在寫入 warehouse 前做 PII redaction（hash user_id、truncate IP、strip email）。<a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">Monitoring 07 去識別化</a> 的策略同時適用於 hot 和 warm path。</p>
<h2 id="讀者路由">讀者路由</h2>
<table>
  <thead>
      <tr>
          <th>如果你想</th>
          <th>先讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>理解 event 格式設計</td>
          <td><a href="/blog/monitoring/02-log-schema/" data-link-title="模組二：Log Schema 設計" data-link-desc="跨平台統一事件格式、欄位設計、版本演進策略">Monitoring 02 Log Schema</a></td>
      </tr>
      <tr>
          <td>理解行為分析方法</td>
          <td><a href="/blog/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">Monitoring 08 Business Analytics</a></td>
      </tr>
      <tr>
          <td>理解訊號治理和成本控制</td>
          <td><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">Backend 04 Cardinality 治理</a>、<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></td>
      </tr>
      <tr>
          <td>理解 pipeline 分流架構</td>
          <td><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 分層治理">Backend 04 Telemetry Pipeline</a></td>
      </tr>
      <tr>
          <td>理解 PII 去識別化</td>
          <td><a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">Monitoring 07 Security Privacy</a></td>
      </tr>
      <tr>
          <td>理解 client-to-server 端到端觀測串接</td>
          <td><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 的完整實作鏈路">Backend 04 Client-to-Server 觀測串接</a></td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item></channel></rss>