<?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>Global-Tables on Tarragon</title><link>https://tarrragon.github.io/blog/tags/global-tables/</link><description>Recent content in Global-Tables 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/global-tables/index.xml" rel="self" type="application/rss+xml"/><item><title>DynamoDB Global Tables：multi-region active-active、LWW conflict 與 cross-device sync 正向用例</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/global-tables-conflict/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/global-tables-conflict/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB&lt;/a> overview 的 implementation-layer deep article。寫作參照 &lt;a href="https://tarrragon.github.io/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;p>B2B SaaS 跟客戶 SLA 寫 99.99%、單 region 跑了一年遇過兩次 region-level outage、合計 downtime 已逼近 SLA 上限。team 要把核心 table 改 Global Tables active-active、首問是「multi-region write 之後資料還會一致嗎」。這個問題的答案是：&lt;em>不會、但有工程解法&lt;/em>；DynamoDB Global Tables 用 LWW（Last Writer Wins）跨 region async 同步、conflict 偵測跟 reconciliation 要 application 自己加。&lt;/p>
&lt;p>但 Global Tables 不只是 conflict 痛點。Disney+ 用同一個機制處理 cross-device sync（手機看一半回家用電視繼續）、Genesys 用同一個機制做 15 region B2B 客服平台的 99.999% 可用性。本文先講正向 access pattern（避免讓讀者誤以為 Global Tables 只是「跨 region 寫入會 conflict、所以痛苦」）、再展開 conflict resolution 跟 reconciliation 設計。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Workload 適配本 vendor 才繼續&lt;/strong>：DynamoDB 4 軸判讀（PK 天然均勻 / control plane vs data plane / consistency 可接受 eventual / access pattern 穩定）軸見 &lt;a href="../single-table-design-pattern/#dynamodb-%e9%81%a9%e7%94%a8%e5%ba%a6%e5%89%8d%e7%bd%ae%e5%88%a4%e8%ae%804-%e8%bb%b8">single-table-design-pattern 開頭 4 軸前置判讀&lt;/a>。Global Tables 是 &lt;em>已選 DynamoDB 後&lt;/em> 的拓樸決策；strong global consistency 必要的 workload 應走 Spanner / Cosmos DB strong consistency level、不是用 LWW 補。&lt;/p>&lt;/blockquote>
&lt;h2 id="b2b-saas-vs-b2c-業務-driver-對比">B2B SaaS vs B2C 業務 driver 對比&lt;/h2>
&lt;p>Global Tables 不是預設選擇、是 &lt;em>業務性質&lt;/em> 決定的工程投資。&lt;code>9.C24 Genesys&lt;/code> 揭露兩條關鍵 frame — 可用性目標的業務 driver、跟每多一個 9 的 cost 指數成長。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>業務性質&lt;/th>
 &lt;th>典型可用性目標&lt;/th>
 &lt;th>年停機容忍&lt;/th>
 &lt;th>Multi-region 投資邏輯&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>B2C 大型網站&lt;/td>
 &lt;td>99.9%&lt;/td>
 &lt;td>8.76 小時&lt;/td>
 &lt;td>通常單 region + PITR / cross-region backup 划算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B2B SaaS&lt;/td>
 &lt;td>99.95% 或 99.99%（合約）&lt;/td>
 &lt;td>4.4 小時 / 52.6 分鐘&lt;/td>
 &lt;td>合約義務、客戶 SLA 違約有金錢損失、ROI 正向&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客服平台類&lt;/td>
 &lt;td>99.999%（合約客戶）&lt;/td>
 &lt;td>5.26 分鐘&lt;/td>
 &lt;td>客戶停線損失極大、15 region 投資合理（Genesys）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>B2C 大型網站&lt;/strong>通常 99.9% SLA、年停機 8.76 小時可接受、單 region + PITR + cross-region backup 是常見配置；改 Global Tables 邊際成本高、ROI 通常不正向。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB</a> overview 的 implementation-layer deep article。寫作參照 <a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology</a>。</p></blockquote>
<p>B2B SaaS 跟客戶 SLA 寫 99.99%、單 region 跑了一年遇過兩次 region-level outage、合計 downtime 已逼近 SLA 上限。team 要把核心 table 改 Global Tables active-active、首問是「multi-region write 之後資料還會一致嗎」。這個問題的答案是：<em>不會、但有工程解法</em>；DynamoDB Global Tables 用 LWW（Last Writer Wins）跨 region async 同步、conflict 偵測跟 reconciliation 要 application 自己加。</p>
<p>但 Global Tables 不只是 conflict 痛點。Disney+ 用同一個機制處理 cross-device sync（手機看一半回家用電視繼續）、Genesys 用同一個機制做 15 region B2B 客服平台的 99.999% 可用性。本文先講正向 access pattern（避免讓讀者誤以為 Global Tables 只是「跨 region 寫入會 conflict、所以痛苦」）、再展開 conflict resolution 跟 reconciliation 設計。</p>
<blockquote>
<p><strong>Workload 適配本 vendor 才繼續</strong>：DynamoDB 4 軸判讀（PK 天然均勻 / control plane vs data plane / consistency 可接受 eventual / access pattern 穩定）軸見 <a href="../single-table-design-pattern/#dynamodb-%e9%81%a9%e7%94%a8%e5%ba%a6%e5%89%8d%e7%bd%ae%e5%88%a4%e8%ae%804-%e8%bb%b8">single-table-design-pattern 開頭 4 軸前置判讀</a>。Global Tables 是 <em>已選 DynamoDB 後</em> 的拓樸決策；strong global consistency 必要的 workload 應走 Spanner / Cosmos DB strong consistency level、不是用 LWW 補。</p></blockquote>
<h2 id="b2b-saas-vs-b2c-業務-driver-對比">B2B SaaS vs B2C 業務 driver 對比</h2>
<p>Global Tables 不是預設選擇、是 <em>業務性質</em> 決定的工程投資。<code>9.C24 Genesys</code> 揭露兩條關鍵 frame — 可用性目標的業務 driver、跟每多一個 9 的 cost 指數成長。</p>
<table>
  <thead>
      <tr>
          <th>業務性質</th>
          <th>典型可用性目標</th>
          <th>年停機容忍</th>
          <th>Multi-region 投資邏輯</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>B2C 大型網站</td>
          <td>99.9%</td>
          <td>8.76 小時</td>
          <td>通常單 region + PITR / cross-region backup 划算</td>
      </tr>
      <tr>
          <td>B2B SaaS</td>
          <td>99.95% 或 99.99%（合約）</td>
          <td>4.4 小時 / 52.6 分鐘</td>
          <td>合約義務、客戶 SLA 違約有金錢損失、ROI 正向</td>
      </tr>
      <tr>
          <td>客服平台類</td>
          <td>99.999%（合約客戶）</td>
          <td>5.26 分鐘</td>
          <td>客戶停線損失極大、15 region 投資合理（Genesys）</td>
      </tr>
  </tbody>
</table>
<p><strong>B2C 大型網站</strong>通常 99.9% SLA、年停機 8.76 小時可接受、單 region + PITR + cross-region backup 是常見配置；改 Global Tables 邊際成本高、ROI 通常不正向。</p>
<p><strong>B2B SaaS</strong> 99.95% 或 99.99% SLA 多半寫進合約、違約有具體金錢損失；Global Tables 的 N region cost 對比 SLA 違約成本通常 ROI 正向。critical 的是 <em>合約義務</em> 不是 <em>技術完美</em>。</p>
<p><strong>客服平台類</strong> 99.999% 是極端可用性目標、年停機 5.26 分鐘、Genesys 撐 8000+ orgs 的客服平台、客戶停線損失極大、跨 15 region 的 active-active 是合理投資。但 <em>不是每個 SaaS 都該追 99.999%</em>、是 <em>業務性質決定下限</em>。</p>
<p><strong>成本對比</strong>（<code>9.C24</code> 揭露）：15 region 成本約 = 1 region 的 15x（base table cost）+ 跨 region replication WCU。每多一個 9、容量規劃跟運維成本指數成長。</p>
<blockquote>
<p><strong>Scope warning（指標口徑紀律）</strong>：99.999% 是「12 個月滾動歷史值、不代表未來持續達成」（<code>9.C24</code> 警惕段第 1 條）。可用性是滾動指標、不是恆久承諾。引用 Genesys 99.999% 數字時要明示口徑（滾動 / customer-facing），不要寫成「DynamoDB 保證 99.999%」。</p></blockquote>
<h2 id="正向-access-pattern不只-conflict-議題">正向 access pattern：不只 conflict 議題</h2>
<p>Global Tables 不只是 DR / availability、也是正向 access pattern 的工程方案。先建立正向用例的判讀、再進 conflict 細節。</p>
<p><strong>Cross-device sync</strong>（<code>9.C27 Disney+</code> 揭露）：用戶在手機看到一半、晚上回家用電視繼續、播放進度跨裝置同步。Global Tables 自然解這個 access pattern — 用戶在不同 region 登入同帳號、寫入自動同步、最終一致性可接受場景。</p>
<p><strong>Global read（latency 優化）</strong>：跨地域用戶讀取就近 region 副本、latency 從 200ms 降到 &lt; 10ms。read 比 write 多很多倍的 workload（feed / catalog / user profile）受益最大。</p>
<p><strong>DR failover</strong>：region-level outage 時 application 切到 secondary region 繼續服務、RTO 通常 &lt; 5 分鐘（DNS / routing 切換時間、不含 application 端 reconnect）。</p>
<p><strong>B2C 也可能划算的場景</strong>：cross-device sync 是 <em>user-facing experience</em>、不是合規 / SLA driver。B2C 大規模平台（Disney+ / Spotify 類）也可能投資 Global Tables。判讀軸是「sync 體驗是否核心 UX」、不只「合約 SLA」。</p>
<h2 id="核心機制lww-conflict-resolution">核心機制：LWW conflict resolution</h2>
<p>Global Tables 的 first-class concept：</p>
<ul>
<li><strong>Multi-region active-active</strong>：每個 region 都能寫、async replication；typical replication latency &lt; 1s 但 <em>無 SLA</em></li>
<li><strong>LWW by wall clock</strong>：conflict 由 attribute <code>aws:rep:updatetime</code> 決定、純物理時間；不是 logical clock、不是 vector clock</li>
<li><strong>同 region read-your-write</strong>：本 region 寫立即可讀（同 region quorum 內）、其他 region 看到要等 replication</li>
<li><strong>Capacity 獨立</strong>：每個 region 自己的 RCU/WCU、<code>ReplicatedWriteCapacityUnits</code> 是跨 region replication 額外 WCU、按 region 數倍計</li>
</ul>
<p>對應 knowledge card：<a href="/blog/backend/knowledge-cards/consistency-level/" data-link-title="Consistency Level" data-link-desc="資料系統對讀寫一致性語意的可選擇層級">consistency level</a>、<a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">rto</a>、<a href="/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">rpo</a>。</p>
<h2 id="設計流程">設計流程</h2>
<p>從 access pattern 分類到 reconciliation pipeline 的 6 步流程。</p>
<h4 id="step-1access-pattern-分類">Step 1：access pattern 分類</h4>
<p>把 table 中的資料分兩類：</p>
<ul>
<li><strong>region-pinned data</strong>：user 主要 region（合規 / 地理 affinity）；不啟用 Global Tables、用 region-pinned cluster</li>
<li><strong>global data</strong>：跨 region read / cross-device sync；啟用 Global Tables</li>
</ul>
<p>不是所有 table 都該上 Global Tables；user profile 跨 region 同步、但用戶交易紀錄可能該 pin 在合規 region。</p>
<h4 id="step-2啟用-global-tables">Step 2：啟用 Global Tables</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">aws dynamodb update-table <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --table-name orders <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --replica-updates <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  <span class="s1">&#39;[{&#34;Create&#34;: {&#34;RegionName&#34;: &#34;us-east-1&#34;}}]&#39;</span></span></span></code></pre></div><p>加 region 後 vendor 自動 backfill；backfill 期間 capacity 雙倍（原 region + 新 region 同步流量）、要預留 capacity buffer。</p>
<h4 id="step-3application-寫入策略">Step 3：application 寫入策略</h4>
<p>兩種寫入策略：</p>
<ul>
<li><strong>home region write</strong>：每 user 固定一個 home region 寫、避免 conflict；user 跨 region 漫遊時透過 routing 仍寫 home</li>
<li><strong>nearest region write</strong>：latency 優先、user 寫就近 region；conflict 機率高、必須加 idempotency 跟 reconciliation</li>
</ul>
<p>選擇：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>寫入策略</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>user profile / 設定</td>
          <td>home region write</td>
          <td>conflict 少、簡單</td>
      </tr>
      <tr>
          <td>cross-device sync</td>
          <td>nearest region write</td>
          <td>用戶在不同裝置同時操作、容忍 LWW</td>
      </tr>
      <tr>
          <td>訂單 / 金流</td>
          <td>home region write</td>
          <td>業務不容許 conflict 損失</td>
      </tr>
  </tbody>
</table>
<h4 id="step-4idempotency-設計">Step 4：idempotency 設計</h4>
<p>每筆 write 加 <code>request_id</code> 或 <code>client_timestamp</code>、application 端去重：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">write_with_idempotency</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="n">action</span><span class="p">,</span> <span class="n">request_id</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">table</span><span class="o">.</span><span class="n">put_item</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="n">Item</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">            <span class="s2">&#34;PK&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;USER#</span><span class="si">{</span><span class="n">user_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            <span class="s2">&#34;SK&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;ACTION#</span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="s2">#</span><span class="si">{</span><span class="n">request_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="s2">&#34;ts&#34;</span><span class="p">:</span> <span class="n">datetime</span><span class="o">.</span><span class="n">utcnow</span><span class="p">()</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="s2">&#34;request_id&#34;</span><span class="p">:</span> <span class="n">request_id</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">ConditionExpression</span><span class="o">=</span><span class="s2">&#34;attribute_not_exists(request_id)&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">)</span></span></span></code></pre></div><p><code>ConditionExpression</code> 在同一 region 內擋重複；跨 region eventual 仍可能 race，conflict 落到 LWW + reconciliation。</p>
<blockquote>
<p><strong>Scope warning（重要）</strong>：「加 request_id 或 client_timestamp」具體實作屬通用工程知識、<code>9.C26 PayPay</code> case 揭露「通知不可丟失」的需求分層、<em>沒有</em> 揭露具體 idempotency 實作。引用 PayPay 時要降溫成「PayPay 揭露需求分層（通知 vs 訊息）、idempotency 為通用工程實作」、不寫成「PayPay 使用 request_id」（陷阱 4：把通用工程實作寫成 case 揭露）。</p></blockquote>
<h4 id="step-5conflict-detection">Step 5：conflict detection</h4>
<p>DynamoDB Streams 訂閱、Lambda 比較 <code>aws:rep:updatetime</code> 跟 application timestamp、抓出可疑 conflict 進 reconciliation queue：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">detect_conflict</span><span class="p">(</span><span class="n">stream_event</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">new_image</span> <span class="o">=</span> <span class="n">stream_event</span><span class="p">[</span><span class="s2">&#34;dynamodb&#34;</span><span class="p">][</span><span class="s2">&#34;NewImage&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">repl_time</span> <span class="o">=</span> <span class="n">new_image</span><span class="p">[</span><span class="s2">&#34;aws:rep:updatetime&#34;</span><span class="p">][</span><span class="s2">&#34;S&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">app_time</span> <span class="o">=</span> <span class="n">new_image</span><span class="p">[</span><span class="s2">&#34;client_timestamp&#34;</span><span class="p">][</span><span class="s2">&#34;S&#34;</span><span class="p">]</span>
</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">    <span class="k">if</span> <span class="nb">abs</span><span class="p">(</span><span class="n">parse</span><span class="p">(</span><span class="n">repl_time</span><span class="p">)</span> <span class="o">-</span> <span class="n">parse</span><span class="p">(</span><span class="n">app_time</span><span class="p">))</span> <span class="o">&gt;</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">seconds</span><span class="o">=</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="c1"># 可疑 conflict、進 reconciliation</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">sqs</span><span class="o">.</span><span class="n">send_message</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">QueueUrl</span><span class="o">=</span><span class="n">RECONCILIATION_QUEUE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">MessageBody</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">stream_event</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="p">)</span></span></span></code></pre></div><blockquote>
<p><strong>Scope warning</strong>：DynamoDB Streams 用法屬通用工程實作、<code>9.C26 PayPay</code> case <em>沒有</em> 明示用 Streams、引用時要分層（PayPay 揭露需求、Streams 是工程實作的標準解）。</p></blockquote>
<h4 id="step-6reconciliation-pipeline">Step 6：reconciliation pipeline</h4>





<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">Conflict event → SQS queue → Lambda / human review → merge logic → write back</span></span></code></pre></div><p>merge logic 視業務而定：</p>
<ul>
<li>訂單金額 conflict：抓最大值（避免少收）</li>
<li>用戶設定 conflict：抓最新（user-facing 行為一致）</li>
<li>watchlist conflict：union（兩裝置加的都保留）</li>
</ul>
<p><strong>驗證點</strong>：DR drill 演 region outage、確認 secondary region 接管後 read / write 都正常；<code>ReplicationLatency</code> p99 &lt; 1s。</p>
<p><strong>Rollback boundary</strong>：region 可逐個移除、但 active-active 改 active-passive 期間 application 需配合路由切換；先 application 切再移 region、不可同時做。</p>
<h2 id="失敗模式">失敗模式</h2>
<p>實際部署常見的 5 種失敗：</p>
<h4 id="case-1lww-默默吃掉-write">Case 1：LWW 默默吃掉 write</h4>
<p>跨 region 同一 record concurrent update、後到的 write 因 timestamp 較大蓋過先到的；business 看到「我送出的更新沒了」、稽核 log 才發現 conflict。修法：critical write 加 <code>ConditionExpression</code> 比較 <code>version</code> attribute、conflict 時 application 端 retry + merge；不要依賴 LWW 作為 conflict 解。</p>
<h4 id="case-2clock-skew-讓-lww-倒置">Case 2：Clock skew 讓 LWW 倒置</h4>
<p>region A 寫入 timestamp 因 NTP skew 比 region B 後寫快 200ms、結果舊資料贏。修法：依靠 application timestamp + monotonic counter、不依賴 server wall clock；critical write 用 conditional version + retry。</p>
<blockquote>
<p><strong>Scope warning</strong>：「200ms NTP skew」具體數字屬通用工程估算、case 未揭露具體 skew 範圍。</p></blockquote>
<h4 id="case-3replication-lag-撞-slo">Case 3：Replication lag 撞 SLO</h4>
<p>大 batch write 期間 replication lag 從 1s 變 30s、跨 region read 看到 30s 前資料、application 端 user 操作異常。修法：偵測 <code>ReplicationLatency</code> 升高時 application 端切 home region read、避免跨 region eventual read；把 replication lag 加進 SLO 監控、設 alarm。</p>
<h4 id="case-4dr-切換後-stale-data-持續-propagate">Case 4：DR 切換後 stale data 持續 propagate</h4>
<p>primary region outage 切到 secondary、舊 primary 恢復後仍把 outdated data 推回去、覆蓋 secondary 期間的新寫入。修法：DR runbook 含「舊 primary 恢復後人工 reconciliation 或重建」step、不可全自動 catch-up；舊 primary 恢復前先確認 replication 方向是「從 secondary catch up」而非「推舊資料回 secondary」。</p>
<h4 id="case-5跨-region-transaction-失敗">Case 5：跨 region transaction 失敗</h4>
<p>application 試圖跨 region <code>TransactWriteItems</code>、API 不支援跨 region transaction、原子性破裂。修法：transaction 限同 region 內、跨 region 用 <a href="/blog/backend/knowledge-cards/saga/" data-link-title="Saga" data-link-desc="處理跨服務分散事務的補償型 transaction 序列、用最終一致換 ACID atomic">saga</a> + idempotent + reconciliation；不要把同 region 的 transaction 假設搬到跨 region。</p>
<p><strong>Anti-recommendation</strong>：single-region availability 已達 99.95% + RTO 可接受 1 小時 + 預算敏感（特別 B2C 場景）→ 用 PITR + 跨 region backup 而非 Global Tables；Global Tables cost = N × single region cost 不止（對應 B2B vs B2C driver 對比）。</p>
<h2 id="容量與觀測">容量與觀測</h2>
<p>CloudWatch metric：</p>
<ul>
<li><code>ReplicationLatency</code>：p99 通常 &lt; 1s、建議 SLO 設 5s alarm</li>
<li><code>PendingReplicationCount</code>：積壓量、batch write 期間會升高</li>
<li><code>ReplicatedWriteCapacityUnits</code>：跨 region replication 額外 WCU、按 region 數倍計</li>
</ul>
<p>DynamoDB Streams + Lambda：抓 conflict event、寫進獨立 audit table；reconciliation job 從 audit table 跑、不直接動 base table。</p>
<p><strong>Region-level dashboard</strong>：每個 region 獨立 capacity / latency / error rate panel；DR drill 看是否能在 RTO 內切換。</p>
<p><strong>Cost monitoring</strong>：</p>
<ul>
<li>Global Tables cost ≈ N region × base cost + replication WCU</li>
<li>4 region 成本約 4.5x single region；15 region（Genesys 規模）約 15x</li>
<li>每多一個 region 都要重新算 ROI（軸 6 vendor crossover 的延伸）</li>
</ul>
<p><strong>指標口徑紀律</strong>（重要）：99.99% / 99.999% SLA 是 <em>滾動指標 + 歷史值</em>、不是永久承諾；引用 Genesys 99.999% 時明示「12 個月滾動 / customer-facing」、不寫成「DynamoDB 保證 99.999%」。</p>
<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>、<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>。</p>
<h2 id="邊界與整合">邊界與整合</h2>
<h3 id="frame-5region-pinned-global-tables-吸收合規邊界">Frame 5：region-pinned Global Tables 吸收合規邊界</h3>
<p>Global Tables 不只是高可用工具、也是 <em>合規邊界</em>（<a href="/blog/backend/knowledge-cards/data-residency/" data-link-title="Data Residency" data-link-desc="合規要求資料留在特定地理邊界內、跨境複製違反合規、推動 fleet 拓樸決策">Data Residency</a> 拓樸）的吸收層。DynamoDB 在 vendor capability 層級支援 <em>region-pinned replication</em> — 每張 table 可獨立決定哪些 region 參與 replication group、部分 region 可不加入。這個 capability 同時服務三類場景：合規分離（受監管市場資料不跨境）、cost / latency 取捨（資料只在主要服務 region 同步）、災備拓樸（少數 region 純讀備援）。<code>9.C24 Genesys</code> 15 region 揭露的是 <em>延遲就近接入</em> 的 B2B SaaS 拓樸（客戶服務延遲敏感、必須在客戶所在地有 region）— case 原文沒明示合規應用、但 region-pinned capability 在 Genesys 規模下天然能容納合規市場分離、是同 capability 的 <em>可能應用維度</em>、不是 case 已驗證的具體實踐。</p>
<p>跨 vendor 對照：</p>
<table>
  <thead>
      <tr>
          <th>Vendor</th>
          <th>合規吸收機制</th>
          <th>拓樸特性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DynamoDB</td>
          <td>region-pinned Global Tables（按 region 開關 replication、各市場可分離）</td>
          <td>仍是 active-active、但 replication 範圍可控</td>
      </tr>
      <tr>
          <td>Aurora</td>
          <td>fleet 拓樸（每市場獨立 cluster、合規禁止跨境 = Global Database 反指標）</td>
          <td>active-passive per market、跨市場不複製</td>
      </tr>
      <tr>
          <td>CockroachDB</td>
          <td>locality + placement（邏輯一個 cluster + region pinning + Outposts）</td>
          <td>單 logical cluster、physical row 鎖在合規 region</td>
      </tr>
      <tr>
          <td>MongoDB / Cosmos DB</td>
          <td>cluster-per-region（無 row-level locality 等價物、整 cluster 切割）</td>
          <td>各 region 獨立 cluster、application 層做市場 routing</td>
      </tr>
  </tbody>
</table>
<p><strong>為什麼 DynamoDB 在這個 frame 退化得最輕</strong>：Global Tables 的 region 開關是 <em>attribute 級</em> 設計（每張 table 可獨立決定哪些 region 參與）、不像 Aurora 必須整 cluster 拆。讀者要把「跨境合規 + 高可用」雙重需求兼顧時、DynamoDB 是最少結構性改造的路徑 — 但代價是 LWW conflict 跟 reconciliation 設計仍要自己做。</p>
<p><strong>何時 region-pinned 而非 active-active</strong>：受監管金融 / 個資跨境禁止的市場（如 GDPR strict 條款區、中國個資法 PIPL、巴西 LGPD）— 該 region 仍開 DynamoDB table、但 <em>不加入 Global Tables replication group</em>、跟其他 region 完全切割。capability 設計上支援這種按 region 開關 replication 的拓樸；具體是否套用、要看 <em>讀者自己的市場合規清單</em>、不是把 Genesys 規模當必然證據（Genesys case 揭露的是延遲就近接入、未明示合規分離實踐）。</p>
<h3 id="disney-vs-genesys兩種-global-tables-工程動機">Disney+ vs Genesys：兩種 Global Tables 工程動機</h3>
<p><code>9.C27 Disney+</code> 跟 <code>9.C24 Genesys</code> 是 Global Tables 兩種不同的工程動機：</p>
<ul>
<li><strong>Disney+</strong>：cross-device sync 是 user-facing UX、watchlist + 播放進度跨裝置同步、B2C 但 sync 是 core experience</li>
<li><strong>Genesys</strong>：99.999% B2B SaaS 合約義務、15 region active-active、客服平台停線損失極大</li>
</ul>
<p>兩個 case 都用 Global Tables、但動機完全不同 — Disney+ 是 UX driver、Genesys 是合約 driver。寫進你自己的設計時要明示自己屬哪一型，因為兩種型別的 cost 容忍度跟 conflict 容忍度完全不同。</p>
<h3 id="sibling-與-cross-link">Sibling 與 cross-link</h3>
<ul>
<li><a href="/blog/backend/01-database/vendors/dynamodb/consistency-model-optimization/" data-link-title="DynamoDB Strongly Consistent → Eventually Consistent：same protocol, different contract" data-link-desc="DynamoDB consistency model 從 strongly consistent read 改 eventually consistent read 是 50% cost 優化但風險集中在 application contract — 同 vendor / 同 protocol / 同 table / 不同 read consistency；驗證 [#128](/report/data-topology-as-audit-dimension/) self-aware limitation 提出的 consistency axis 候選；涵蓋 read pattern audit / 5 個 production 踩雷">consistency-model-optimization</a> — 同 region eventual / strong 取捨、本篇是跨 region 延伸</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">on-demand-vs-provisioned</a> — 多 region capacity 規劃放大、軸 5 工時釋放在 multi-region 更顯著</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">partition-key-antipatterns</a> — hot partition 跨 region 同樣存在、每個 region 的 partition 都要均勻</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">single-table-design-pattern</a> — single-table 設計在 multi-region 仍適用、access pattern 反推 PK/SK 不變</li>
<li>替代路由：global strong consistency 必要 → Spanner / Cosmos DB strong consistency level</li>
<li>Migration playbook：single-region → Global Tables 屬 topology re-layout、對應 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration playbook methodology</a> Type F</li>
<li>跟 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">Genesys 9.C24</a> 互引：15 region 5 個 9 可用性的工程實踐 + B2B SaaS 業務 driver</li>
<li>跟 <a href="/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">Disney+ 9.C27</a> 互引：cross-device sync 作為正向 access pattern</li>
<li>跟 <a href="/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">PayPay 9.C26</a> 互引：揭露需求分層（通知 vs 訊息）、idempotency / Streams 為通用工程實作、PayPay 未公開揭露具體實作</li>
</ul>
]]></content:encoded></item></channel></rss>