<?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>External-Consistency on Tarragon</title><link>https://tarrragon.github.io/blog/tags/external-consistency/</link><description>Recent content in External-Consistency on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 27 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/external-consistency/index.xml" rel="self" type="application/rss+xml"/><item><title>Spanner TrueTime API 深度：GPS + 原子鐘、commit wait、為什麼 line-rate scaling 才是設計目的</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/truetime-api-depth/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/truetime-api-depth/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/" data-link-title="Google Cloud Spanner" data-link-desc="全球分散式 strong-consistency OLTP、TrueTime API、線性擴展到 10 億 req/sec">Cloud Spanner&lt;/a> overview 的 implementation-layer deep article。Overview 已說明 Spanner 在全球 OLTP 譜系的定位、本文聚焦 &lt;em>TrueTime API&lt;/em> — Spanner 用來消滅 single coordinator bottleneck、換到 line-rate scaling 的核心機制。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="商業邏輯先行truetime-是手段line-rate-scaling-才是目的">商業邏輯先行：TrueTime 是手段、line-rate scaling 才是目的&lt;/h2>
&lt;p>TrueTime 的設計目的是消滅 single coordinator bottleneck、讓 OLTP 拿到 line-rate scaling — external consistency 只是這條路徑上拿到的副產品。讀者若把 TrueTime 當成「一個保證 external consistency 的精巧時間 trick」、會誤把工具當目標、後續所有 commit wait / Paxos / GPS 細節都解錯方向。&lt;/p>
&lt;p>傳統 OLTP（PostgreSQL、MySQL、Cloud SQL）跨節點交易要靠一個 coordinator 決定全局順序、coordinator 本身就是 bottleneck。&lt;code>1x node = 1x throughput&lt;/code> 的線性擴展在 single-primary 模型撞牆、想 scale 只能往應用層 sharding 走、付管理 shard key / 跨 shard query / resharding 的代價。Spanner 換掉這條路徑：TrueTime 把 wall-clock 變成跨 datacenter 可比較的 &lt;em>interval&lt;/em>、Paxos 把 coordinator 變成「拓樸感知的多 leader」（每個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/range-sharding/" data-link-title="Range Sharding" data-link-desc="分散式 SQL 把 key space 切成可自動 split / merge 的 range、每個 range 自己的 consensus group、application 透明">Range Sharding&lt;/a> split 自己的 Paxos group 各自前進）、commit timestamp 用 TrueTime 對齊到 real-time 順序、不再需要一個全局 coordinator 串行所有 transaction。&lt;/p>
&lt;p>9.C10 Cloud Spanner planetary scale case 揭露的線性擴展證據：「2 nodes → 45K reads/sec、4 nodes → 90K reads/sec」是 Spanner 設計目標的直接證據、不只是 marketing 數字。這條揭露 Spanner external consistency 不是「加強版 serializable isolation」、是「coordinator 換拓樸」的 paradigm shift。寫到這裡讀者該意識到一件事：選 Spanner 不是選一個更貴更強的 SQL、是選一條 &lt;em>把 coordinator 拆掉&lt;/em> 的 scaling 路徑。&lt;/p>
&lt;p>&lt;strong>Dogfood 邊界（本文反覆強調）&lt;/strong>：9.C10 是 Google internal dogfood case、不是 customer-facing capacity 參考。「10 億 req/sec」是 Google 全使用者加總、不是單一 instance 配額；「2 nodes → 45K reads / 4 nodes → 90K reads」是 Google internal benchmark 揭露的線性擴展 &lt;em>模式&lt;/em>、不是客戶 SLA 承諾。本文後續所有 9.C10 數字引用都會明示這條邊界、避免讀者誤把 dogfood 當配額。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/spanner/" data-link-title="Google Cloud Spanner" data-link-desc="全球分散式 strong-consistency OLTP、TrueTime API、線性擴展到 10 億 req/sec">Cloud Spanner</a> overview 的 implementation-layer deep article。Overview 已說明 Spanner 在全球 OLTP 譜系的定位、本文聚焦 <em>TrueTime API</em> — Spanner 用來消滅 single coordinator bottleneck、換到 line-rate scaling 的核心機制。</p></blockquote>
<hr>
<h2 id="商業邏輯先行truetime-是手段line-rate-scaling-才是目的">商業邏輯先行：TrueTime 是手段、line-rate scaling 才是目的</h2>
<p>TrueTime 的設計目的是消滅 single coordinator bottleneck、讓 OLTP 拿到 line-rate scaling — external consistency 只是這條路徑上拿到的副產品。讀者若把 TrueTime 當成「一個保證 external consistency 的精巧時間 trick」、會誤把工具當目標、後續所有 commit wait / Paxos / GPS 細節都解錯方向。</p>
<p>傳統 OLTP（PostgreSQL、MySQL、Cloud SQL）跨節點交易要靠一個 coordinator 決定全局順序、coordinator 本身就是 bottleneck。<code>1x node = 1x throughput</code> 的線性擴展在 single-primary 模型撞牆、想 scale 只能往應用層 sharding 走、付管理 shard key / 跨 shard query / resharding 的代價。Spanner 換掉這條路徑：TrueTime 把 wall-clock 變成跨 datacenter 可比較的 <em>interval</em>、Paxos 把 coordinator 變成「拓樸感知的多 leader」（每個 <a href="/blog/backend/knowledge-cards/range-sharding/" data-link-title="Range Sharding" data-link-desc="分散式 SQL 把 key space 切成可自動 split / merge 的 range、每個 range 自己的 consensus group、application 透明">Range Sharding</a> split 自己的 Paxos group 各自前進）、commit timestamp 用 TrueTime 對齊到 real-time 順序、不再需要一個全局 coordinator 串行所有 transaction。</p>
<p>9.C10 Cloud Spanner planetary scale case 揭露的線性擴展證據：「2 nodes → 45K reads/sec、4 nodes → 90K reads/sec」是 Spanner 設計目標的直接證據、不只是 marketing 數字。這條揭露 Spanner external consistency 不是「加強版 serializable isolation」、是「coordinator 換拓樸」的 paradigm shift。寫到這裡讀者該意識到一件事：選 Spanner 不是選一個更貴更強的 SQL、是選一條 <em>把 coordinator 拆掉</em> 的 scaling 路徑。</p>
<p><strong>Dogfood 邊界（本文反覆強調）</strong>：9.C10 是 Google internal dogfood case、不是 customer-facing capacity 參考。「10 億 req/sec」是 Google 全使用者加總、不是單一 instance 配額；「2 nodes → 45K reads / 4 nodes → 90K reads」是 Google internal benchmark 揭露的線性擴展 <em>模式</em>、不是客戶 SLA 承諾。本文後續所有 9.C10 數字引用都會明示這條邊界、避免讀者誤把 dogfood 當配額。</p>
<p><strong>Fact vs derive 分層警告</strong>：本段「coordinator bottleneck → TrueTime + Paxos」frame 是跨 Spanner 2012 OSDI 論文 + 公開文件（2024-2026）+ 9.C10 case 合成的工程 frame、不是 9.C10 case 直接展開實作層細節。9.C10 案例直接揭露的 fact 是線性擴展數字跟 dogfood 邊界；本文 derive 的 frame 是「為什麼傳統 OLTP coordinator 是 bottleneck」。引用時這條分層在每段引用具體數字時都會重申。</p>
<h2 id="問題情境跨-region-oltp-的順序漏洞">問題情境：跨 region OLTP 的順序漏洞</h2>
<p>跨 region OLTP 想保證「全球用戶看到的交易順序跟 wall clock 一致」、但 NTP 同步誤差動輒 10-100ms、足夠讓 region A 已 commit 的計費事件被 region B 看到一個更新的 timestamp 卻是舊狀態。讀者徵兆通常從這幾個地方浮現：分散式系統團隊在 Cloud SQL / Aurora 多 region 上做 read replica、發現「跨 region read 順序顛倒」、audit log timestamp 不可靠、reconcile 對帳對不上、業務以為自己用了 transaction 就有「強一致」、實際只有 single-node 的 serializable isolation。</p>
<p>真實壓力場景：Google Ads 計費需要把每筆扣款事件放進可驗證的 <em>外部</em> 順序、不只是 transaction 內部 serializable。讀者若把這套需求帶回自家系統、會發現一條共同訊號 — 「兩個 transaction 都 commit 成功、用戶體感卻違反順序」這種事故、不是 isolation level 的問題、是 <em>external consistency</em> 的問題。</p>
<p>Case anchor：<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Cloud Spanner planetary scale</a> — Google Ads / Play 訂閱 / Search 計費跟 TrueTime 綁定。<strong>dogfood 邊界明示</strong>：9.C10 是 Google 內部 dogfood case、不是 customer-facing capacity 參考；引用其揭露的線性 scaling 模式時要分清「設計目標證據」vs「客戶可獲得配額」。</p>
<h2 id="核心機制truetime-的-api-跟硬體基礎">核心機制：TrueTime 的 API 跟硬體基礎</h2>
<p>TrueTime 對外只有兩個 primitive — <code>TT.now()</code> 回傳一個 <em>interval</em> <code>[earliest, latest]</code>、不是單一時刻；<code>TT.after(t)</code> / <code>TT.before(t)</code> 判斷一個事件是否確定在 t 之後 / 之前。整個 external consistency 演算法都建立在「時間是一個 interval、不是一個點」這個 API 設計上。</p>
<h3 id="硬體基礎gps--原子鐘冗餘">硬體基礎：GPS + 原子鐘冗餘</h3>
<p>每個 datacenter 部署 GPS 接收器 + 原子鐘（armageddon master、用來防 GPS 全網干擾）、time master 之間互相比對排除離群值、TrueTime daemon 從多個 master 拉時間並算 worst-case bound。GPS 給 absolute time reference、原子鐘給 short-term stability（GPS 短暫失聯時仍能用 drift bound 撐過去）。雙來源是為了把 ε 的失敗模式限制在「絕大多數時間 ε ≤ 7ms、極端事件下 ε spike 但不會無限制漂移」。</p>
<h3 id="不確定性-εepsilon">不確定性 ε（epsilon）</h3>
<p>跨 datacenter 同步 + clock drift 估計、ε 目標維持在 1-7ms 區間。</p>
<p><strong>Fact source 分層警告</strong>：1-7ms 是 Google 2012 OSDI 論文 + Spanner 公開文件（2024-2026）引用的範圍、9.C10 dogfood case 未直接揭露 production ε 分布。引用時這組數字明標「來自 Spanner vendor docs / 2012 論文、不是 9.C10 case 直接揭露」、避免讀者把兩種來源混為一談。</p>
<h3 id="commit-wait-機制external-consistency-的核心">Commit wait 機制：external consistency 的核心</h3>
<p>read-write transaction 要拿 commit timestamp s 時、Spanner 設 <code>s = TT.now().latest</code>、然後 <em>等待</em> 直到 <code>TT.after(s)</code> 才回 ACK。這段「等」就是 <a href="/blog/backend/knowledge-cards/commit-wait/" data-link-title="Commit Wait" data-link-desc="Spanner external consistency 的核心機制 — read-write transaction 拿 commit timestamp s 後等到 TT.after(s) 才 ACK、wait ≈ 2ε、付 latency tax 換 commit 順序 = real-time 順序">Commit Wait</a> — Spanner 特有的物理延遲、由 TrueTime ε 主導、跟 <a href="/blog/backend/knowledge-cards/cross-region-quorum/" data-link-title="Cross-Region Quorum" data-link-desc="multi-region distributed SQL 強制 voting replica 跨 region、commit 等多 region quorum ack、跨洲 RTT 物理硬限">Cross-Region Quorum</a> 的網路 RTT 是兩個獨立的延遲來源、不能混算。</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">T1 開始 commit            T1 確定可回 ACK
</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">       v                          v
</span></span><span class="line"><span class="ln">4</span><span class="cl">TT.now().earliest .... s = TT.now().latest .... TT.after(s)
</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">                            |---------- commit wait ≈ ε ----------|
</span></span><span class="line"><span class="ln">7</span><span class="cl">       |---------- total commit wait ≈ 2ε（從拿 s 那刻開始） ---------|</span></span></code></pre></div><p>commit wait ≈ 2ε 的數學保證了「下一個 transaction 拿到的 timestamp 一定 &gt; s」、external consistency 的全序性質就由這個 wait 撐住。<strong>Fact source 分層</strong>：commit wait ≈ 2ε 的推導來自 Spanner 2012 OSDI 論文 + 官方文件、不是 9.C10 case 直接展開實作層數學。引用這條數學要附「來源 vendor docs / paper」、避免讀者誤以為這是 case 揭露。</p>
<h3 id="跟通用-linearizability-卡片的差異">跟通用 linearizability 卡片的差異</h3>
<p><a href="/blog/backend/knowledge-cards/linearizability/" data-link-title="Linearizability" data-link-desc="每次操作看起來都在單一全域順序中即時生效的一致性語意">Linearizability</a> 只要求「存在某個全序」、external consistency 進一步要求「全序跟 real-time 順序一致」。TrueTime 是把後者變可實作的關鍵 — 它把跨 datacenter 的「real-time 順序」變成可機械判定的 <code>TT.after(s)</code>、不需要全局 coordinator 來決定誰先誰後。對應的概念卡：<a href="/blog/backend/knowledge-cards/external-consistency/" data-link-title="External Consistency" data-link-desc="交易可見順序與外部真實時間順序一致的強一致性語意">external-consistency</a>、<a href="/blog/backend/knowledge-cards/linearizability/" data-link-title="Linearizability" data-link-desc="每次操作看起來都在單一全域順序中即時生效的一致性語意">linearizability</a>、<a href="/blog/backend/knowledge-cards/quorum/" data-link-title="Quorum" data-link-desc="分散式系統以多數節點同意作為提交或讀取有效性的門檻">quorum</a>。</p>
<h2 id="操作流程怎麼觀測-ε-跟調用-truetime">操作流程：怎麼觀測 ε 跟調用 TrueTime</h2>
<p>TrueTime 本身不對外暴露給 application 操作、ε / commit wait 由 Spanner 內部執行。團隊能做的是 <em>觀測</em> ε 跟 <em>選擇</em> 不同強度的 read consistency。</p>
<h3 id="觀測-ε">觀測 ε</h3>
<p>Cloud Monitoring metric <code>spanner.googleapis.com/instance/clock_skew_ms</code> 是 ε 的對外指標、判讀正常 &lt; 7ms、異常 spike &gt; 50ms 代表 time master 失聯或 GPS 干擾。把這條 metric 跟 <code>commit_latencies</code> p99 配成 evidence pair：ε spike 時 commit latency heatmap 應該整層平移、若 commit latency 動但 ε 沒動、不是 TrueTime 的問題、是 quorum / network 的問題。</p>
<h3 id="跨-region-instance-配置時的-truetime-影響">跨 region instance 配置時的 TrueTime 影響</h3>
<p>voting region 越分散、ε 上限越高、commit wait 越長 → write latency 直接受 ε 影響。multi-region instance config 在做 region layout 決策時要把「voting region 散布範圍」當 latency budget 的固定支出、不是配完才補觀測。</p>
<h3 id="read-only-transaction-的-staleness-選項">read-only transaction 的 staleness 選項</h3>





<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">strong              → 等 TrueTime 確認可讀最新、付完整 commit wait + quorum cost
</span></span><span class="line"><span class="ln">2</span><span class="cl">exact_staleness(t)  → 讀 t 秒前快照、避開 commit wait、適合 reporting / analytics
</span></span><span class="line"><span class="ln">3</span><span class="cl">bounded_staleness(t)→ 容忍 t 秒、可讀最近的本地 replica 副本、不跨 region quorum</span></span></code></pre></div><p>stale / bounded staleness 走的是 Spanner 版的 <a href="/blog/backend/knowledge-cards/follower-read/" data-link-title="Follower Read" data-link-desc="分散式 SQL 從 non-voting replica 讀 closed timestamp 之前的資料、不參與 Raft commit、低 latency 但 read-after-write 場景仍可能 stale">Follower Read</a> — 本地 replica serve 不參與 commit 的 read、避開跨 region quorum 把 read latency 降到 single-region 等級。</p>
<p>三者 trade-off 在 SDK 層顯式設定、不是 isolation level：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Spanner Go SDK 範例（time-sensitive、查最新文件確認 API）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">client</span><span class="p">.</span><span class="nf">Single</span><span class="p">().</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nf">WithTimestampBound</span><span class="p">(</span><span class="nx">spanner</span><span class="p">.</span><span class="nf">MaxStaleness</span><span class="p">(</span><span class="mi">10</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)).</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nf">Query</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">statement</span><span class="p">)</span></span></span></code></pre></div><h3 id="驗證點跟-rollback-boundary">驗證點跟 rollback boundary</h3>
<p>跑 cross-region write + cross-region read benchmark、量 p50 / p99 write latency、確認 ≈ 2ε + quorum RTT 的數量級。TrueTime 配置不由用戶調、commit wait 由 Spanner 自動執行；應用層 rollback boundary 在「改用 stale read / bounded staleness」而不是「關掉 TrueTime」 — TrueTime 是 Spanner 內部不可關的機制、不是 feature flag。</p>
<h2 id="失敗模式ε-暴衝跟誤用-strong-read">失敗模式：ε 暴衝跟誤用 strong read</h2>
<h3 id="ε-暴衝time-master-失聯">ε 暴衝（time master 失聯）</h3>
<p>GPS 干擾、datacenter time master 雙故障、ε 從 4ms 跳到 200ms → 所有 write 的 commit wait 暴增、p99 write latency 從 50ms 變 500ms。徵兆是 Cloud Monitoring <code>commit_latencies</code> heatmap 整層平移、<code>clock_skew_ms</code> 同步上升。根因不在 application、在 datacenter 物理層、修法是等 GCP 內部 time master 恢復、應用層只能臨時降到 bounded staleness 救 read path。</p>
<h3 id="把-strong-read-用在不需要的路徑">把 strong read 用在不需要的路徑</h3>
<p>報表、analytics、user profile fetch 全用 strong read、每次 read 都付 TrueTime 對齊代價、p99 read 跟 write 同步退化。徵兆是 <code>commit_latencies</code> 沒動、但 <code>api/request_latencies</code> for <code>ExecuteSql</code> 整體上升。修法是把 read path 分類、reporting / analytics 改 bounded staleness、保留 strong read 給「讀後決策再寫」的 critical path。</p>
<h3 id="在-client-側做自己的-timestamp">在 client 側做「自己的 timestamp」</h3>
<p>application 用 <code>time.Now()</code> 當業務 key、跨 region 寫入時 client clock skew 直接破壞順序 — Spanner 內部 external consistency 對、業務層卻錯。徵兆是對帳系統發現 timestamp 順序顛倒、但 Spanner audit log 都 OK。修法是業務層 timestamp 全改用 Spanner <code>PENDING_COMMIT_TIMESTAMP</code> sentinel、commit 時由 Spanner 填、不靠 client clock。</p>
<h3 id="把-spanner-當-single-region-sql-用卻配-multi-region-instance">把 Spanner 當 single-region SQL 用、卻配 multi-region instance</h3>
<p>每筆 write 都付跨洲 quorum + commit wait、cost 跟 latency 都浪費。徵兆是 instance config 是 multi-region 但實際 read 99% 來自單一 region、write 也是。修法是降到 regional instance、把跨 region 需求改用 read-only replica 或 export 到 BigQuery。</p>
<h3 id="ε-沒監控">ε 沒監控</h3>
<p>團隊直到事故才看 clock_skew metric、被動處理而非主動告警。建議 <code>clock_skew_ms &gt; 20ms</code> warn、<code>&gt; 50ms</code> page、跟 commit_latencies p99 偏離 baseline 2x 一起當 saturation discovery 訊號（回 <a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a>）。</p>
<h2 id="容量與觀測truetime-ε-是-latency-budget-的固定支出">容量與觀測：TrueTime ε 是 latency budget 的固定支出</h2>
<p>必看 metric：</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">commit_latencies (p50 / p95 / p99)        → commit wait + quorum RTT 的總和
</span></span><span class="line"><span class="ln">2</span><span class="cl">api/request_count by method               → strong read vs stale read 的分布
</span></span><span class="line"><span class="ln">3</span><span class="cl">instance/cpu/utilization_by_priority      → high / low priority 分流
</span></span><span class="line"><span class="ln">4</span><span class="cl">clock_skew_ms                             → TrueTime ε 的對外指標</span></span></code></pre></div><p>用 <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> 框架把 TrueTime ε 跟 commit latency 配成 evidence pair。Capacity 規劃路由回 <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>、把「ε × write rate」當 latency budget 的固定支出 — 寫越多筆、commit wait 累積成本越高、不是 free。</p>
<p>Alert 建議：</p>
<table>
  <thead>
      <tr>
          <th>Metric</th>
          <th>Warn</th>
          <th>Page</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>clock_skew_ms</code></td>
          <td>&gt; 20ms</td>
          <td>&gt; 50ms</td>
      </tr>
      <tr>
          <td><code>commit_latencies</code> p99</td>
          <td>baseline 1.5x</td>
          <td>baseline 2x</td>
      </tr>
      <tr>
          <td><code>low_priority_utilization</code></td>
          <td>&gt; 80%</td>
          <td>&gt; 90%</td>
      </tr>
  </tbody>
</table>
<h3 id="line-rate-scaling-驗證呼應商業邏輯先行段">Line-rate scaling 驗證（呼應商業邏輯先行段）</h3>
<p>擴 node 數時量「read throughput / node」是否維持線性 — 9.C10 揭露的 2 → 4 nodes = 45K → 90K reads/sec 是 Google internal dogfood 的線性模式、不是客戶 SLA 承諾。團隊在自己 instance 上要驗證的不是「能不能達到 90K reads」、是「擴 node 後 throughput / node 有沒有保持線性」。若曲線 sub-linear、檢查是否 hot split / hot range / Paxos group 不均、TrueTime 機制本身不解這層。</p>
<h2 id="邊界與整合何時不用-truetime或不用-spanner">邊界與整合：何時不用 TrueTime（或不用 Spanner）</h2>
<h3 id="何時改用-stale-read">何時改用 stale read</h3>
<p>reporting / analytics / dashboard 場景改用 bounded staleness 換 cost、不付 commit wait 的 latency tax。判準：若這個 read path 用 5 秒前的資料不會影響業務決策、改 stale read；若會、保留 strong read。</p>
<h3 id="何時不該升-spanner">何時不該升 Spanner</h3>
<p>單 region workload 不該為了 external consistency 升 Spanner、Cloud SQL + serializable isolation 已經夠。9.C10 dogfood 揭露的線性 scaling 是「跨 region + 大規模」場景的設計目標、單 region 用戶拿不到對應的 cost / latency benefit。詳見遷移判讀：<a href="../migrate-from-cloud-sql-pg/">Cloud SQL → Spanner Migration Playbook</a> 的 no-go condition 段。</p>
<h3 id="sibling-deep-articles-路由">Sibling deep articles 路由</h3>
<ul>
<li><a href="../consistency-models-comparison/">consistency-models-comparison</a>：為什麼 external consistency ≠ serializability ≠ linearizability、line-rate scaling 對照表、cross-region quorum 100-200ms 物理硬限</li>
<li><a href="../schema-migration-interleaved-tables/">schema-migration-interleaved-tables</a>：schema change 也用 TrueTime 保證 version 邊界、parent-child storage layout</li>
<li><a href="../migrate-from-cloud-sql-pg/">migrate-from-cloud-sql-pg</a>：cutover 階段需要把 application 對 timestamp 的假設審一遍（特別是 client 端 <code>time.Now()</code> 那條失敗模式）</li>
</ul>
<h3 id="跟-1x-章節的互引">跟 1.x 章節的互引</h3>
<ul>
<li><a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a>：Spanner 是 PC 系統的代表、Cosmos DB AP 系統當對照</li>
<li><a href="/blog/backend/knowledge-cards/transaction-boundary/" data-link-title="Transaction Boundary" data-link-desc="說明哪些資料變更應在同一個交易中一起成功或一起回復">transaction boundary</a>：external consistency 是 transaction boundary 的全球延伸</li>
</ul>
<h3 id="anti-recommendation">Anti-recommendation</h3>
<p>讀者讀完本文應該能判斷：TrueTime 不是「保證強一致」的功能、是「換 scaling 路徑」的核心；若團隊只想要「強一致」、不需要「跨節點線性擴展」、PostgreSQL serializable + 應用層補上 client-side ordering 就夠、不必為 TrueTime 付 GCP lock-in 的 cost。</p>
]]></content:encoded></item><item><title>Spanner Consistency Models 對照：external consistency vs serializability vs linearizability</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/consistency-models-comparison/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/consistency-models-comparison/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/" data-link-title="Google Cloud Spanner" data-link-desc="全球分散式 strong-consistency OLTP、TrueTime API、線性擴展到 10 億 req/sec">Cloud Spanner&lt;/a> overview 的 concept-layer deep article。Overview 已說明 Spanner 在強一致 SQL 譜系的定位、本文聚焦 &lt;em>consistency model&lt;/em> — 三個常被混用的概念（external consistency / serializability / linearizability）的精確差異、line-rate scaling 對照、跟 cross-region quorum 的物理硬限。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="問題情境五個詞混用的選型困境">問題情境：五個詞混用的選型困境&lt;/h2>
&lt;p>團隊在 Spanner / CockroachDB / Aurora DSQL 之間選型、看文件講 strict serializability、external consistency、linearizable、snapshot isolation、serializable — 五個詞混用、不確定買的是哪一種保證。讀者徵兆通常是「我們需要強一致」但說不出強到哪、把 serializable transaction 跟 linearizable read 當同一件事、debug 對帳時發現「兩個 transaction 都 commit 成功、順序卻違反 user 體感」。&lt;/p>
&lt;p>真實壓力場景：金融帳本 — A 在台北轉帳給 B、B 在東京立即收到通知然後查餘額、結果查到「轉帳前」的餘額。serializable 允許這種行為（兩 transaction 可以排成任意順序、不要求跟 wall clock 一致）、external consistency 不允許（必須等 commit 後的順序符合 real-time）。混用兩個詞會讓選型結論在系統實作後才被推翻、那時候改架構成本已經高了。&lt;/p>
&lt;p>Case anchor：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Cloud Spanner planetary scale&lt;/a> — Google Ads 計費需要 external consistency；對照 PostgreSQL SSI、CockroachDB HLC、Aurora DSQL。&lt;strong>dogfood 邊界明示&lt;/strong>：9.C10 是 Google 內部 dogfood case、不是 customer-facing capacity 參考；本文引用其 line-rate scaling 數字時要附「Google internal dogfood 揭露的設計目標、不是客戶 SLA」邊界。&lt;/p>
&lt;h2 id="三個概念的精確定義">三個概念的精確定義&lt;/h2>
&lt;h3 id="serializability">Serializability&lt;/h3>
&lt;p>transaction 的執行結果等同於 &lt;em>某個&lt;/em> 序列順序執行；不要求順序跟 real-time 一致。PostgreSQL SERIALIZABLE isolation level（SSI 實作）給的就是這個保證。它解決的問題是 &lt;em>concurrent transaction 之間互相干擾的 anomaly&lt;/em>（dirty read / lost update / write skew / G2-item）、不解決「跨 transaction 的 wall-clock 順序」。&lt;/p>
&lt;p>範例：A 在 10:00:00 commit T1（餘額 +100）、B 在 10:00:01 commit T2（查餘額）。serializable 允許系統把 T2 排在 T1 之前、B 看到舊餘額 — 兩 transaction 都成功、isolation 沒被破壞、但用戶體感違反順序。&lt;/p>
&lt;h3 id="linearizability">Linearizability&lt;/h3>
&lt;p>單一 object 操作有全序、且全序跟 real-time wall-clock 一致。只談 single-object、不談跨 object transaction。DynamoDB strongly consistent read 是 single-item linearizability、Redis &lt;code>INCR&lt;/code> 是 single-key linearizability。對應 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/linearizability/" data-link-title="Linearizability" data-link-desc="每次操作看起來都在單一全域順序中即時生效的一致性語意">linearizability&lt;/a> 卡。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/spanner/" data-link-title="Google Cloud Spanner" data-link-desc="全球分散式 strong-consistency OLTP、TrueTime API、線性擴展到 10 億 req/sec">Cloud Spanner</a> overview 的 concept-layer deep article。Overview 已說明 Spanner 在強一致 SQL 譜系的定位、本文聚焦 <em>consistency model</em> — 三個常被混用的概念（external consistency / serializability / linearizability）的精確差異、line-rate scaling 對照、跟 cross-region quorum 的物理硬限。</p></blockquote>
<hr>
<h2 id="問題情境五個詞混用的選型困境">問題情境：五個詞混用的選型困境</h2>
<p>團隊在 Spanner / CockroachDB / Aurora DSQL 之間選型、看文件講 strict serializability、external consistency、linearizable、snapshot isolation、serializable — 五個詞混用、不確定買的是哪一種保證。讀者徵兆通常是「我們需要強一致」但說不出強到哪、把 serializable transaction 跟 linearizable read 當同一件事、debug 對帳時發現「兩個 transaction 都 commit 成功、順序卻違反 user 體感」。</p>
<p>真實壓力場景：金融帳本 — A 在台北轉帳給 B、B 在東京立即收到通知然後查餘額、結果查到「轉帳前」的餘額。serializable 允許這種行為（兩 transaction 可以排成任意順序、不要求跟 wall clock 一致）、external consistency 不允許（必須等 commit 後的順序符合 real-time）。混用兩個詞會讓選型結論在系統實作後才被推翻、那時候改架構成本已經高了。</p>
<p>Case anchor：<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Cloud Spanner planetary scale</a> — Google Ads 計費需要 external consistency；對照 PostgreSQL SSI、CockroachDB HLC、Aurora DSQL。<strong>dogfood 邊界明示</strong>：9.C10 是 Google 內部 dogfood case、不是 customer-facing capacity 參考；本文引用其 line-rate scaling 數字時要附「Google internal dogfood 揭露的設計目標、不是客戶 SLA」邊界。</p>
<h2 id="三個概念的精確定義">三個概念的精確定義</h2>
<h3 id="serializability">Serializability</h3>
<p>transaction 的執行結果等同於 <em>某個</em> 序列順序執行；不要求順序跟 real-time 一致。PostgreSQL SERIALIZABLE isolation level（SSI 實作）給的就是這個保證。它解決的問題是 <em>concurrent transaction 之間互相干擾的 anomaly</em>（dirty read / lost update / write skew / G2-item）、不解決「跨 transaction 的 wall-clock 順序」。</p>
<p>範例：A 在 10:00:00 commit T1（餘額 +100）、B 在 10:00:01 commit T2（查餘額）。serializable 允許系統把 T2 排在 T1 之前、B 看到舊餘額 — 兩 transaction 都成功、isolation 沒被破壞、但用戶體感違反順序。</p>
<h3 id="linearizability">Linearizability</h3>
<p>單一 object 操作有全序、且全序跟 real-time wall-clock 一致。只談 single-object、不談跨 object transaction。DynamoDB strongly consistent read 是 single-item linearizability、Redis <code>INCR</code> 是 single-key linearizability。對應 <a href="/blog/backend/knowledge-cards/linearizability/" data-link-title="Linearizability" data-link-desc="每次操作看起來都在單一全域順序中即時生效的一致性語意">linearizability</a> 卡。</p>
<p>linearizability 跟 serializability 是 <em>正交</em> 的兩個概念 — linearizability 講「單一 object 的 real-time 順序」、serializability 講「transaction 的 anomaly-free 執行」。一個系統可以是 linearizable 但不 serializable（單 object 強保證、跨 object transaction 沒有）、也可以是 serializable 但不 linearizable（PostgreSQL SSI single-node 在 replica lag 後就不 linearizable）。</p>
<h3 id="external-consistency--strict-serializability">External consistency / Strict serializability</h3>
<p>transaction 層級的 serializability + 全序跟 real-time 一致 — 等同於把 linearizability 推廣到 multi-object transaction。Spanner 用 TrueTime + commit wait 實作、保證 commit timestamp 順序 = real-time 順序。對應 <a href="/blog/backend/knowledge-cards/external-consistency/" data-link-title="External Consistency" data-link-desc="交易可見順序與外部真實時間順序一致的強一致性語意">external-consistency</a> 卡。</p>
<p>回到金融帳本例：external consistency 不允許 T2 排在 T1 之前、因為 T2 的 transaction timestamp 必須大於 T1 的 commit timestamp、用戶查餘額必看到 +100 後的金額。</p>
<h2 id="line-rate-scaling-對照為什麼-pg-serializable-在-multi-node-拿不到-line-rate">Line-rate scaling 對照：為什麼 PG serializable 在 multi-node 拿不到 line-rate</h2>
<p>這段的核心責任是回答「為什麼 Spanner 不只是『更強的 serializable』、是『coordinator 換拓樸』的 paradigm shift」、扣 <a href="../truetime-api-depth/">truetime-api-depth</a> 的商業邏輯先行 frame。讀者選 consistency 等級時、實際在選「系統的 scaling 路徑」、不只是「應用層 anomaly 哪些被排除」。</p>
<h3 id="9c10-揭露的線性擴展數字">9.C10 揭露的線性擴展數字</h3>
<p>「2 nodes → 45K reads/sec、4 nodes → 90K reads/sec」這條線性 scaling 揭露 Spanner external consistency 不是「加強版 serializable」、是把跨節點 coordinator 從 single-point 換成「拓樸感知的多 leader（每個 split 自己的 Paxos group）」、所以擴 node 數可以線性拿 throughput。</p>
<p><strong>Dogfood 邊界明示</strong>：9.C10 數字是 Google internal dogfood、不是 customer-facing capacity 承諾。客戶能拿到的 line-rate 受 instance config、region layout、workload shape 影響、不會自動複製 Google 內部曲線。</p>
<h3 id="對照表四個系統的-scaling-路徑">對照表：四個系統的 scaling 路徑</h3>
<table>
  <thead>
      <tr>
          <th>系統</th>
          <th>Isolation / Consistency 等級</th>
          <th>Multi-node scaling 路徑</th>
          <th>為什麼撞天花板（或不撞）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PostgreSQL SSI</td>
          <td>Serializable</td>
          <td>single-primary + read replica</td>
          <td>寫只能 single primary、跨節點交易要 2PC + coordinator、replica 寫不了；scaling 路徑停在 single-primary 容量上限</td>
      </tr>
      <tr>
          <td>CockroachDB</td>
          <td>Serializable + per-key linearizable</td>
          <td>range-based + HLC</td>
          <td>range coordinator 仍存在、但 range 拆細了；retry contract 接住跨 range conflict、扣 serializable restart cost</td>
      </tr>
      <tr>
          <td>Spanner</td>
          <td>External consistency</td>
          <td>split-based + Paxos + TrueTime</td>
          <td>coordinator 變多 leader、TrueTime 對齊 commit 順序、線性擴展是設計目標（9.C10 揭露 dogfood 線性模式）</td>
      </tr>
      <tr>
          <td>Aurora DSQL</td>
          <td>Strong consistency（2024 推出）</td>
          <td>文件未完全公開、查最新 docs</td>
          <td>時間敏感 claim、本文不擴寫；讀者實作前查官方文件確認最新 scaling 模型</td>
      </tr>
  </tbody>
</table>
<p>每個欄位都要回到具體的 scaling 機制讀。PostgreSQL SSI 跟「single-primary」綁定 — 想 scale write 只能 sharding；CockroachDB 把 range 拆細、coordinator 分布到 range 層、但跨 range conflict 還是會 trigger retry；Spanner 用 Paxos group per split、commit timestamp 用 TrueTime 對齊、不需要全局 coordinator 來決定順序；Aurora DSQL 是新系統、機制細節隨版本演進。</p>
<h3 id="為什麼這個對照寫進-consistency-文章不是純機制文章">為什麼這個對照寫進 consistency 文章、不是純機制文章</h3>
<p>讀者選 consistency 等級時、實際在選「系統的 scaling 路徑」、不只是「應用層 anomaly 哪些被排除」。external consistency 的 cost 包含 commit wait latency、但 benefit 包含 line-rate scaling — 兩者要一起講、不能拆開。把對照表放這裡、讓 consistency 跟 scaling 在同一段被讀者一起判讀、避免「我們需要強一致」這種需求被翻譯成「升級到 Spanner」這種跳號決策。</p>
<h2 id="cross-region-quorum-100-200ms-物理硬限強一致--全球不是免費">Cross-region quorum 100-200ms 物理硬限：強一致 + 全球不是免費</h2>
<p><a href="/blog/backend/knowledge-cards/cross-region-quorum/" data-link-title="Cross-Region Quorum" data-link-desc="multi-region distributed SQL 強制 voting replica 跨 region、commit 等多 region quorum ack、跨洲 RTT 物理硬限">Cross-Region Quorum</a> + external consistency + multi-region 不是「免費全球」、是「用 latency 換 consistency」。讀者若沒看到具體數量級、會誤把 Spanner 當作「強一致 + 全球 + 低延遲」的奇蹟、實際 cross-region write 在物理光速硬限下必須付跨洲 round-trip cost。</p>
<h3 id="9c10-揭露的數量級">9.C10 揭露的數量級</h3>
<p>「external consistency 必須等多區 quorum、跨洲交易延遲可達 100-200ms」 — 這是 9.C10 case 直接揭露的工程數字、不是本章 derive。<strong>Dogfood 邊界明示</strong>：9.C10 case 揭露的是 Google internal dogfood 觀察到的數量級、不是 SLA 承諾；實際客戶的 cross-region write latency 隨 voting region 配置、network path 變化。</p>
<h3 id="latency-拆解模型cross-region-write">Latency 拆解模型（cross-region write）</h3>





<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">total write latency ≈ 2ε（[Commit Wait](/backend/knowledge-cards/commit-wait/)、TrueTime ε 兩倍 ≈ 2-14ms）
</span></span><span class="line"><span class="ln">2</span><span class="cl">                    + quorum RTT across voting regions
</span></span><span class="line"><span class="ln">3</span><span class="cl">                       跨洲：50-100ms one-way、來回 100-200ms
</span></span><span class="line"><span class="ln">4</span><span class="cl">                       跨大陸內：10-30ms
</span></span><span class="line"><span class="ln">5</span><span class="cl">                       跨 zone（同 region）：&lt; 5ms
</span></span><span class="line"><span class="ln">6</span><span class="cl">                    + Spanner internal processing</span></span></code></pre></div><p>跨洲 quorum 在這個模型裡是 <em>dominant term</em>、不是 <a href="/blog/backend/knowledge-cards/commit-wait/" data-link-title="Commit Wait" data-link-desc="Spanner external consistency 的核心機制 — read-write transaction 拿 commit timestamp s 後等到 TT.after(s) 才 ACK、wait ≈ 2ε、付 latency tax 換 commit 順序 = real-time 順序">commit wait</a> — 判讀時要明示「commit wait 跟跨 region quorum 是兩個獨立的物理 cost、不能混用一個 latency 數字解釋兩者」。讀者常見的誤解是把 100-200ms 寫成「Spanner commit wait」、實際 commit wait 只是其中 2-14ms、剩下 100ms+ 是物理光速限定的 quorum RTT。</p>
<h3 id="scope-warning實際-latency-依-region-配置">Scope warning：實際 latency 依 region 配置</h3>
<p>100-200ms 是 9.C10 case 揭露的範圍、實際 latency 隨 voting region 配置變化：</p>
<table>
  <thead>
      <tr>
          <th>Instance config 類型</th>
          <th>Voting region 散布</th>
          <th>典型 write p99</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Regional（單 region 多 zone）</td>
          <td>同 region 內</td>
          <td>&lt; 10ms</td>
      </tr>
      <tr>
          <td>Dual-region（同大陸）</td>
          <td>跨大陸內</td>
          <td>20-50ms</td>
      </tr>
      <tr>
          <td>Multi-region（跨洲）</td>
          <td>跨大陸或跨洲</td>
          <td>100-200ms</td>
      </tr>
  </tbody>
</table>
<p>引用要附條件「跨洲多 region instance、實際數字依 region 配置」、不能寫成「Spanner cross-region write 一律 100-200ms」。讀者拿這條 latency anchor 做 capacity planning 時、必須先 audit 自家 instance 是哪種 config、不能套用 100-200ms 當基線。</p>
<h2 id="ssot-對齊strong--multi-region-互斥議題不在此處展開">SSoT 對齊：Strong + multi-region 互斥議題不在此處展開</h2>
<p>Strong consistency + multi-region 互斥議題（包含 Cosmos DB 5 levels 的 Strong + multi-region 限制）的 SSoT 是 <a href="/blog/backend/01-database/vendors/cosmosdb/multi-region-write-conflict/" data-link-title="Cosmos DB Multi-Region Write：active-active、LWW、custom merge、Strong &#43; multi-region 互斥的 AP 取捨" data-link-desc="Multi-region active-active write 的 conflict resolution（LWW / custom merge / conflict feed）、Strong 跟 multi-region write 為什麼互斥、廣告 SLA vs 實測可用性鏈路拆解 — 從 Minecraft Earth &#43; Toyota Connected 切入">Cosmos DB multi-region-write-conflict</a>。本篇 cross-link 不展開、避免重複展開同議題。</p>
<p>本篇展開的子議題：</p>
<ul>
<li>external consistency / serializability / linearizability 的精確定義差異</li>
<li>Spanner external consistency 的 TrueTime 實作機制（細節在 <a href="../truetime-api-depth/">truetime-api-depth</a>）</li>
<li>cross-region quorum 的物理 cost 數量級</li>
<li>line-rate scaling 對照表（為什麼 single-primary 系統拿不到線性）</li>
</ul>
<p>兩個 SSoT 處理同一個讀者問題（強一致 vs multi-region）的不同切面 — 本篇從 <em>系統 scaling 路徑</em> 切入、Cosmos DB 文章從 <em>consistency level 選擇</em> 切入。讀者讀完本篇後若還在問「為什麼 Cosmos DB strong consistency 不能配 multi-region write」、跳 Cosmos DB SSoT。</p>
<h2 id="操作流程怎麼驗證-consistency-等級">操作流程：怎麼驗證 consistency 等級</h2>
<h3 id="決策樹">決策樹</h3>





<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">跨 multi-object transaction 嗎？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├─ 否 → DynamoDB linearizable read / Redis single-key 足夠
</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">   跨 region 寫入嗎？
</span></span><span class="line"><span class="ln">5</span><span class="cl">   ├─ 否 → CockroachDB / PostgreSQL serializable 足夠
</span></span><span class="line"><span class="ln">6</span><span class="cl">   └─ 是 →
</span></span><span class="line"><span class="ln">7</span><span class="cl">      real-time 順序是產品契約嗎？
</span></span><span class="line"><span class="ln">8</span><span class="cl">      ├─ 否 → CockroachDB multi-region 可接受
</span></span><span class="line"><span class="ln">9</span><span class="cl">      └─ 是 → Spanner / Aurora DSQL</span></span></code></pre></div><h3 id="驗證-consistency-等級的方法">驗證 consistency 等級的方法</h3>
<p>跑 Jepsen-style test、寫 read-write workload 跑 anomaly checker、量 dirty write / lost update / write skew / G2 anomaly。production 系統若不能跑完整 Jepsen、至少要在 staging 跑 <em>對應 anomaly 的具體 test case</em> — 例如金融帳本跑「轉帳後立即跨 region 查餘額、能不能看到舊值」這個具體 case、不是只看 isolation level 設定文字。</p>
<h3 id="sdk-層的選擇點">SDK 層的選擇點</h3>





<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">Spanner          → 預設就是 external consistency、read 可降到 bounded staleness
</span></span><span class="line"><span class="ln">2</span><span class="cl">CockroachDB      → 預設 serializable、可選 AS OF SYSTEM TIME 換 stale read
</span></span><span class="line"><span class="ln">3</span><span class="cl">PostgreSQL       → 要顯式 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
</span></span><span class="line"><span class="ln">4</span><span class="cl">DynamoDB         → 預設 eventually consistent、ConsistentRead=true 換強一致</span></span></code></pre></div><p>每個 SDK 的 default 都不同、不能假設「沒設就是強的」。PostgreSQL default 是 READ COMMITTED、write skew 直接漏。</p>
<h3 id="rollback-boundary">Rollback boundary</h3>
<p>若一致性等級從強降到弱、要審計應用層所有讀取點（特別是「讀後決策再寫」的 critical path）。降級不是 config 一行的事、是 audit 一遍應用層假設的事。</p>
<h2 id="失敗模式把-transaction-當強一致的五種誤用">失敗模式：把 transaction 當「強一致」的五種誤用</h2>
<h3 id="把我們用-transaction當強一致">把「我們用 transaction」當「強一致」</h3>
<p>transaction 只保證原子性、不保證 isolation level；預設 isolation 可能是 READ COMMITTED、write skew 直接漏。修法是顯式設定 isolation level、跑對應 anomaly test 驗證、不靠「我們用 transaction」這種口頭契約。</p>
<h3 id="假設-single-node-serializable--distributed-serializable">假設 single-node serializable = distributed serializable</h3>
<p>PostgreSQL SSI 跨 read replica 立刻失效（replica lag）、團隊以為加 replica 還是 serializable。實際 replica 的 read 是 eventually consistent、可能看到舊 snapshot。修法是區分 primary read vs replica read、replica read path 標 <code>bounded staleness</code>、不混用 isolation level 字眼。</p>
<h3 id="跨系統-timestamp-假設">跨系統 timestamp 假設</h3>
<p>service A 用 Spanner、service B 用 Redis、用各自 timestamp 重組事件順序 — service B 的 clock 沒 TrueTime 保證、跨系統 external consistency 不成立。修法是跨系統事件順序要走 <em>單一系統的 timestamp</em> 或 <em>event sequence number</em>、不靠各系統自己的 wall-clock 拼出順序。</p>
<h3 id="把-linearizability-跟-strong-consistency-混用忽略-multi-object-場景">把 linearizability 跟 strong consistency 混用、忽略 multi-object 場景</h3>
<p>DynamoDB strongly consistent read 是 single-item linearizability、不等於跨 item transaction 強一致。團隊以為「我用了 strongly consistent read 就 OK」、實際跨 item 的順序保證沒有。修法是區分 single-object vs multi-object、跨 item 邏輯如果有順序需求、要用 DynamoDB transaction API（付 2x WCU 的 cost）或換到 Spanner。</p>
<h3 id="過度承諾-external-consistency">過度承諾 external consistency</h3>
<p>dashboard / analytics 強寫 strong read、付不必要的 latency tax。修法是把 read path 分類、analytics / reporting 改 bounded staleness、保留 strong read 給 critical path。回 <a href="../truetime-api-depth/">truetime-api-depth</a> 的「把 strong read 用在不需要的路徑」失敗模式。</p>
<h2 id="容量與觀測一致性等級的-latency-量化">容量與觀測：一致性等級的 latency 量化</h2>
<table>
  <thead>
      <tr>
          <th>一致性等級</th>
          <th>latency 影響</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>External consistency（strong）</td>
          <td>baseline = 2ε + quorum RTT</td>
          <td>critical path、金融帳本、計費</td>
      </tr>
      <tr>
          <td>Bounded staleness（5-10s）</td>
          <td>省 commit wait（10-50ms）、可讀本地 replica</td>
          <td>dashboard、reporting</td>
      </tr>
      <tr>
          <td>Eventual</td>
          <td>砍 quorum RTT、只讀本地 replica</td>
          <td>analytics、推薦</td>
      </tr>
  </tbody>
</table>
<p>跨 region 延遲量化（finding F3.15、來源 9.C10）：external consistency + multi-region instance config、跨洲 quorum 把 write latency 推到 100-200ms 數量級；單 region instance 的 commit wait 是 baseline（≈ 2ε ≈ 2-14ms）、跨 region quorum 是額外 dominant cost。</p>
<p>Cloud Monitoring：<code>spanner.googleapis.com/instance/clock_skew_ms</code> 觀察 ε、<code>api/api_request_latencies</code> for <code>Commit</code> 觀察 commit latency 分布；CockroachDB 觀察 <code>sql.txn.restart.serializable</code> 計數（serializable restart 率）。回到 <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> 把一致性等級當 release gate 的一部分。</p>
<p>Capacity 觀點：external consistency 的 commit wait 是「無法 scale away 的 latency 支出」、capacity planning 要先扣這部分；跨 region instance 的 quorum RTT 也是物理硬限、不能透過加 node 解。</p>
<h2 id="邊界與整合sibling-路由跟-anti-recommendation">邊界與整合：sibling 路由跟 anti-recommendation</h2>
<h3 id="sibling-deep-articles">Sibling deep articles</h3>
<ul>
<li><a href="../truetime-api-depth/">truetime-api-depth</a>：external consistency 的硬體基礎、TrueTime ε / commit wait 數學、商業邏輯先行 frame</li>
<li><a href="../schema-migration-interleaved-tables/">schema-migration-interleaved-tables</a>：schema change 的版本一致性也用 TrueTime</li>
<li><a href="../migrate-from-cloud-sql-pg/">migrate-from-cloud-sql-pg</a>：Diff 階段要明確標示一致性等級從 SSI 升到 external consistency 的應用層影響</li>
</ul>
<h3 id="ssot-cross-link">SSoT cross-link</h3>
<p>Strong consistency + multi-region 互斥議題的 SSoT 在 <a href="/blog/backend/01-database/vendors/cosmosdb/multi-region-write-conflict/" data-link-title="Cosmos DB Multi-Region Write：active-active、LWW、custom merge、Strong &#43; multi-region 互斥的 AP 取捨" data-link-desc="Multi-region active-active write 的 conflict resolution（LWW / custom merge / conflict feed）、Strong 跟 multi-region write 為什麼互斥、廣告 SLA vs 實測可用性鏈路拆解 — 從 Minecraft Earth &#43; Toyota Connected 切入">Cosmos DB multi-region-write-conflict</a>、本篇不重複展開。</p>
<h3 id="跟-1x-章節的互引">跟 1.x 章節的互引</h3>
<ul>
<li><a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a>：Spanner 是 PC 系統的代表</li>
<li><a href="/blog/backend/knowledge-cards/transaction-boundary/" data-link-title="Transaction Boundary" data-link-desc="說明哪些資料變更應在同一個交易中一起成功或一起回復">transaction boundary</a>：跨 transaction 順序保證</li>
</ul>
<h3 id="knowledge-card-雙引用">Knowledge card 雙引用</h3>
<ul>
<li><a href="/blog/backend/knowledge-cards/linearizability/" data-link-title="Linearizability" data-link-desc="每次操作看起來都在單一全域順序中即時生效的一致性語意">linearizability</a> — 本文當這張卡的 vendor 應用範例</li>
<li><a href="/blog/backend/knowledge-cards/external-consistency/" data-link-title="External Consistency" data-link-desc="交易可見順序與外部真實時間順序一致的強一致性語意">external-consistency</a> — 本文擴展這張卡的實作機制</li>
<li><a href="/blog/backend/knowledge-cards/isolation-level/" data-link-title="Isolation Level" data-link-desc="說明資料庫交易隔離級別如何影響並發讀寫結果">isolation-level</a> — 本文澄清 isolation level 跟 consistency model 的差異</li>
</ul>
<h3 id="anti-recommendation">Anti-recommendation</h3>
<p>讀者讀完本文應該能判斷：「我們需要強一致」不等於「升級到 Spanner」 — 先問是 single-object 還是 multi-object、是 single region 還是 multi region、real-time 順序是否是產品契約。多數 OLTP workload 用 PostgreSQL serializable 已經夠、為 external consistency 付 GCP lock-in + 跨 region quorum cost 的判準很高。</p>
]]></content:encoded></item></channel></rss>