<?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>Checkout on Tarragon</title><link>https://tarrragon.github.io/blog/tags/checkout/</link><description>Recent content in Checkout 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/checkout/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></channel></rss>