<?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>Cloud-Sql on Tarragon</title><link>https://tarrragon.github.io/blog/tags/cloud-sql/</link><description>Recent content in Cloud-Sql 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/cloud-sql/index.xml" rel="self" type="application/rss+xml"/><item><title>Migration Playbook：Cloud SQL for PostgreSQL → Cloud Spanner</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/migrate-from-cloud-sql-pg/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/spanner/migrate-from-cloud-sql-pg/</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 的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/migration/" data-link-title="Migration" data-link-desc="說明系統如何把資料、流量或結構從舊狀態移到新狀態">migration&lt;/a> playbook。走 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendor-article-spec/" data-link-title="資料庫 Vendor 文章撰寫規格" data-link-desc="把 PostgreSQL 與 MySQL batch 的正文經驗整理成資料庫 vendor overview、deep article 與 migration playbook 的撰寫規格">vendor-article-spec&lt;/a> Migration Playbook 規格 + &lt;a href="https://tarrragon.github.io/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&lt;/a> Type E（paradigm shift）。每階段切換用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/migration-gate/" data-link-title="Migration Gate" data-link-desc="說明遷移流程何時可以進入下一階段或正式切換">migration gate&lt;/a> 把關 — Evidence 段列的證據是 gate 通過條件、不是 nice-to-have。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="driver為什麼遷什麼條件不該遷">Driver：為什麼遷、什麼條件不該遷&lt;/h2>
&lt;h3 id="啟動壓力">啟動壓力&lt;/h3>
&lt;p>single-region Cloud SQL PostgreSQL primary 觸到容量上限（connection、write throughput、storage IOPS、region 故障風險）、產品要求跨 region active-active write、external consistency 是契約而非 nice-to-have。讀者要先確認自己面對的是「real 跨 region write residency」、不是「想用更強的技術」 — driver 段的核心責任是排除空泛動機。&lt;/p>
&lt;h3 id="主要-driver-候選">主要 driver 候選&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Global write residency&lt;/strong>：用戶分散全球、各地寫入本地 region、跨 region 一致性是產品要求&lt;/li>
&lt;li>&lt;strong>External consistency 對帳契約&lt;/strong>：跨 region 交易順序錯誤會導致對帳爆炸（金融、計費、ticketing）&lt;/li>
&lt;li>&lt;strong>單 primary 容量天花板&lt;/strong>：Cloud SQL 最大 instance 仍撐不住、應用層 sharding 是大工程&lt;/li>
&lt;li>&lt;strong>跨 region read latency&lt;/strong>：read 從各地直接打本地 replica、Cloud SQL read replica 受 single-primary 寫入 throughput 限制&lt;/li>
&lt;/ul>
&lt;h3 id="no-go-condition基礎">No-go condition（基礎）&lt;/h3>
&lt;p>流量集中單 region、跨 region 只是 DR 需求 → 維持 Cloud SQL + read replica + cross-region async DR 更便宜。這條 no-go 不複雜、但團隊常被 marketing 推著跳過 — 在自家 traffic dashboard 上 audit 一遍「write 來自哪些 region、各占比多少」、若 90%+ 來自單 region、Spanner 沒有 benefit。&lt;/p>
&lt;h3 id="no-go-conditionsizing-barrier">No-go condition（sizing barrier）&lt;/h3>
&lt;p>小 / 中型 PostgreSQL workload 的成本門檻 — Spanner 早期最小單位 100 processing units（≈ 1 node）對中小負載偏貴、過去是 sizing barrier；2021+ 推出 100 pu 起跳的 granular sizing 後雖然可從小開始、但 100 pu × per-pu monthly cost 加上跨 region replication 仍可能比 Cloud SQL HA 設定貴數倍。&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 的 <a href="/blog/backend/knowledge-cards/migration/" data-link-title="Migration" data-link-desc="說明系統如何把資料、流量或結構從舊狀態移到新狀態">migration</a> playbook。走 <a href="/blog/backend/01-database/vendor-article-spec/" data-link-title="資料庫 Vendor 文章撰寫規格" data-link-desc="把 PostgreSQL 與 MySQL batch 的正文經驗整理成資料庫 vendor overview、deep article 與 migration playbook 的撰寫規格">vendor-article-spec</a> Migration Playbook 規格 + <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 E（paradigm shift）。每階段切換用 <a href="/blog/backend/knowledge-cards/migration-gate/" data-link-title="Migration Gate" data-link-desc="說明遷移流程何時可以進入下一階段或正式切換">migration gate</a> 把關 — Evidence 段列的證據是 gate 通過條件、不是 nice-to-have。</p></blockquote>
<hr>
<h2 id="driver為什麼遷什麼條件不該遷">Driver：為什麼遷、什麼條件不該遷</h2>
<h3 id="啟動壓力">啟動壓力</h3>
<p>single-region Cloud SQL PostgreSQL primary 觸到容量上限（connection、write throughput、storage IOPS、region 故障風險）、產品要求跨 region active-active write、external consistency 是契約而非 nice-to-have。讀者要先確認自己面對的是「real 跨 region write residency」、不是「想用更強的技術」 — driver 段的核心責任是排除空泛動機。</p>
<h3 id="主要-driver-候選">主要 driver 候選</h3>
<ul>
<li><strong>Global write residency</strong>：用戶分散全球、各地寫入本地 region、跨 region 一致性是產品要求</li>
<li><strong>External consistency 對帳契約</strong>：跨 region 交易順序錯誤會導致對帳爆炸（金融、計費、ticketing）</li>
<li><strong>單 primary 容量天花板</strong>：Cloud SQL 最大 instance 仍撐不住、應用層 sharding 是大工程</li>
<li><strong>跨 region read latency</strong>：read 從各地直接打本地 replica、Cloud SQL read replica 受 single-primary 寫入 throughput 限制</li>
</ul>
<h3 id="no-go-condition基礎">No-go condition（基礎）</h3>
<p>流量集中單 region、跨 region 只是 DR 需求 → 維持 Cloud SQL + read replica + cross-region async DR 更便宜。這條 no-go 不複雜、但團隊常被 marketing 推著跳過 — 在自家 traffic dashboard 上 audit 一遍「write 來自哪些 region、各占比多少」、若 90%+ 來自單 region、Spanner 沒有 benefit。</p>
<h3 id="no-go-conditionsizing-barrier">No-go condition（sizing barrier）</h3>
<p>小 / 中型 PostgreSQL workload 的成本門檻 — Spanner 早期最小單位 100 processing units（≈ 1 node）對中小負載偏貴、過去是 sizing barrier；2021+ 推出 100 pu 起跳的 granular sizing 後雖然可從小開始、但 100 pu × per-pu monthly cost 加上跨 region replication 仍可能比 Cloud SQL HA 設定貴數倍。</p>
<p><strong>來源 9.C10「判讀」段第 3 點</strong>：Spanner 早期 100 pu 起跳是 sizing barrier、後來推出 granular sizing 才讓中小負載可從小開始。<strong>Dogfood 邊界明示</strong>：9.C10 case 揭露的 sizing 結構是 Google 內部 dogfood 的 capacity 規劃語言、不是 customer-facing pricing 承諾；客戶實際成本要看當期 Spanner pricing + region + replication config。</p>
<p>觸發 sizing no-go 的條件：</p>
<table>
  <thead>
      <tr>
          <th>信號</th>
          <th>判讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>workload row count &lt; 數百萬</td>
          <td>100 pu 對這個資料量過 over-provision</td>
      </tr>
      <tr>
          <td>QPS &lt; 1000</td>
          <td>100 pu 容量遠超實際 traffic、cost / QPS ratio 高</td>
      </tr>
      <tr>
          <td>單 region 即可滿足合規</td>
          <td>跨 region replication cost 是純浪費</td>
      </tr>
      <tr>
          <td>Cloud SQL HA 設定已 cover SLA</td>
          <td>升 Spanner 沒 marginal benefit</td>
      </tr>
  </tbody>
</table>
<p>觸發任一條 → 強烈建議走 Cloud SQL HA、不升 Spanner。判讀時要把 Cloud SQL HA cost vs Spanner 100 pu cost 對比清楚、避免讀者「想用新技術」而升級。</p>
<h3 id="no-go-condition應用層延遲容忍">No-go condition（應用層延遲容忍）</h3>
<p>應用層延遲容忍 &lt; 50ms write 的 workload 不該升 Spanner — 跨 region Spanner write 在物理光速硬限下達 100-200ms（<a href="../consistency-models-comparison/">consistency-models-comparison</a> 的 cross-region quorum 段）。延遲敏感 workload 升級後會在 p99 直接撞牆、回退時資料已經寫進 Spanner、roll back 成本巨大。</p>
<p><strong>來源 9.C10「判讀」段第 2 點 + 「策略」段第 3 點</strong>：「external consistency 必須等多區 quorum、跨洲交易延遲可達 100-200ms」。<strong>Dogfood 邊界明示</strong>：9.C10 揭露的數量級是 Google internal observation、客戶實際 latency 隨 voting region 配置變化、引用時要附條件。</p>
<p>觸發 latency no-go 的場景：</p>
<ul>
<li>實時報價系統（毫秒級回應）</li>
<li>高頻交易（HFT）</li>
<li>遊戲 leaderboard 寫入</li>
<li>低延遲 OLTP（金融下單、支付路由）</li>
</ul>
<p>觸發任一條 → 強烈建議走 Cloud SQL 單 region、或考慮把 <em>跨 region 一致性需求</em> 重新審視（是否真的需要強一致、能不能改 event-driven async reconcile）。</p>
<h3 id="替代方案排除">替代方案排除</h3>
<ul>
<li><strong>Aurora DSQL</strong>：AWS 生態、若團隊在 GCP、跨雲不合</li>
<li><strong>CockroachDB</strong>：要自管或想 PostgreSQL wire 但不選 GCP 託管時可考慮、本 playbook 不對照</li>
<li><strong>Citus on Cloud SQL</strong>：multi-region write 不是強項、不解 cross-region external consistency 需求</li>
</ul>
<h3 id="case-anchor--dogfood-邊界">Case anchor + dogfood 邊界</h3>
<p><strong>無強 customer case</strong>。9.C10 是 Google 內部 dogfood、不是公開遷移 case；本 playbook 用 Spanner overview 的 PostgreSQL dialect 路徑 + 官方 migration guide + 通用 pattern。引用時必須明示「9.C10 揭露的線性 scaling / line-rate 設計目標是 Spanner 設計依據、不等於客戶遷移後可獲得的 capacity」。</p>
<p>對照 case：<a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered Aurora 受監管 banking</a> — 雖然是 Aurora、不是 Spanner、但揭露「受監管 OLTP 遷移要算合規 lead time」「資料駐留限制 = 容量規劃 per-市場」這兩條結論在 Spanner 遷移同樣適用。讀者若是受監管產業、跨 region instance config 還要疊上 voting region 是否落在合規市場的 audit。</p>
<h2 id="diff-audit6-規格面--sizing--cost-第-7-面">Diff Audit（6 規格面 + sizing / cost 第 7 面）</h2>
<h3 id="schema-diff">Schema diff</h3>
<p>PostgreSQL DDL → Spanner PostgreSQL dialect 對照：</p>
<table>
  <thead>
      <tr>
          <th>PostgreSQL 特性</th>
          <th>Spanner 對應</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>SERIAL</code></td>
          <td>bit-reversed sequence</td>
          <td>改 primary key 策略、避免 hot split</td>
      </tr>
      <tr>
          <td><code>JSONB</code></td>
          <td><code>JSON</code> type</td>
          <td>大部分相容、複雜 path query 重寫</td>
      </tr>
      <tr>
          <td><code>ARRAY</code></td>
          <td><code>ARRAY</code></td>
          <td>OK</td>
      </tr>
      <tr>
          <td><code>PARTITION BY</code></td>
          <td>不直接支援</td>
          <td>改成 interleaved table 或單表</td>
      </tr>
      <tr>
          <td><code>FOREIGN KEY</code></td>
          <td>保留 FK constraint + 考慮 <a href="/blog/backend/knowledge-cards/interleaved-table/" data-link-title="Interleaved Table" data-link-desc="Spanner 把 parent / child table row 物理交錯儲存、parent &#43; child JOIN 不跨 split">Interleaved Table</a></td>
          <td>parent-child access pattern 改 interleaved</td>
      </tr>
      <tr>
          <td><code>B-tree INDEX</code></td>
          <td>OK</td>
          <td>直接遷</td>
      </tr>
      <tr>
          <td><code>GIN / GiST INDEX</code></td>
          <td>不支援</td>
          <td>用 <code>STORING</code> column 取代部分需求、其餘改應用層</td>
      </tr>
      <tr>
          <td><code>CHECK constraint</code></td>
          <td>部分支援（time-sensitive、查最新文件）</td>
          <td>audit 每條 constraint</td>
      </tr>
      <tr>
          <td><code>UDF / stored procedure</code></td>
          <td>少數支援</td>
          <td>改應用層或 client-side compute</td>
      </tr>
      <tr>
          <td><code>TRIGGER</code></td>
          <td>不支援</td>
          <td>改 application 層或 Spanner change streams</td>
      </tr>
  </tbody>
</table>
<p>interleaved table 設計參考 <a href="../schema-migration-interleaved-tables/">schema-migration-interleaved-tables</a>。讀者要在 schema audit 階段就決定哪些 parent-child 該 interleave、避免後悔成本。</p>
<h3 id="operational-diff">Operational diff</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Cloud SQL</th>
          <th>Spanner</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>基礎架構</td>
          <td>VM-based</td>
          <td>API-based</td>
      </tr>
      <tr>
          <td>認證</td>
          <td>postgres user / role</td>
          <td>IAM role / service account</td>
      </tr>
      <tr>
          <td>備份</td>
          <td>pg_dump / pgBackRest</td>
          <td>point-in-time backup（PITR）</td>
      </tr>
      <tr>
          <td>監控</td>
          <td>postgres-flavor（pg_stat_*）</td>
          <td>Cloud Monitoring <code>spanner.*</code></td>
      </tr>
      <tr>
          <td>Connection pool</td>
          <td>PgBouncer</td>
          <td>SDK 內 gRPC pool</td>
      </tr>
      <tr>
          <td>Vacuum</td>
          <td>必要</td>
          <td>不存在（MVCC 機制不同）</td>
      </tr>
      <tr>
          <td>Replication lag</td>
          <td>需監控</td>
          <td>不存在 single-primary 概念</td>
      </tr>
  </tbody>
</table>
<p>不再需要的 Cloud SQL 責任：vacuum、autovacuum tuning、connection pool（PgBouncer）、replication lag 監控、Patroni HA。</p>
<p>新增 Spanner 責任：processing unit capacity 預測、TrueTime ε 觀測（<a href="../truetime-api-depth/">truetime-api-depth</a>）、long-running schema operation 跟蹤、IAM 細粒度權限。</p>
<h3 id="paradigm-diff">Paradigm diff</h3>
<p>從 single-primary OLTP → 跨 region distributed SQL：</p>
<ul>
<li>transaction commit latency：&lt; 5ms → 50-200ms（跨洲、含 <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> + cross-region quorum）</li>
<li>external consistency 是 default（不再是 isolation level 選擇題）</li>
<li>transaction 上限：Cloud SQL 無硬限 → Spanner 10s timeout、要重構成短交易</li>
<li>read consistency：default eventual → default strong、需顯式選 bounded staleness</li>
</ul>
<p>詳細 consistency model 差異看 <a href="../consistency-models-comparison/">consistency-models-comparison</a>。</p>
<h3 id="component-diff">Component diff</h3>
<p>退役：</p>
<ul>
<li>PgBouncer / pgcat（connection pool）</li>
<li>Cloud SQL HA / Patroni cluster</li>
<li>pgBackRest（備份外掛）</li>
<li>Citus extension（若有用）</li>
<li>各種 postgres extension（時間敏感、逐個 audit 是否 Spanner 支援等效）</li>
</ul>
<p>新增：</p>
<ul>
<li>Spanner client library（Go / Java / Node / Python）</li>
<li>Dataflow（用於 bulk export-import）</li>
<li>Datastream / Database Migration Service（用於 CDC catch-up）</li>
<li>Spanner Studio（query UI）</li>
</ul>
<h3 id="application-diff">Application diff</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Cloud SQL（PostgreSQL client）</th>
          <th>Spanner</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ORM</td>
          <td>全 PG ORM 相容</td>
          <td>PostgreSQL dialect 相容部分 ORM、查最新 dialect 支援列表</td>
      </tr>
      <tr>
          <td>Connection model</td>
          <td>process-per-connection（postgres）</td>
          <td>stateless gRPC client（SDK 內 pool）</td>
      </tr>
      <tr>
          <td>Transaction model</td>
          <td>可長交易</td>
          <td>10s timeout、需短交易</td>
      </tr>
      <tr>
          <td>Timestamp 使用</td>
          <td>app 內 <code>now()</code> / <code>CURRENT_TIMESTAMP</code></td>
          <td>改用 <code>PENDING_COMMIT_TIMESTAMP</code> sentinel</td>
      </tr>
      <tr>
          <td>Cursor / prepared statement</td>
          <td>全支援</td>
          <td>部分支援、查 SDK 文件</td>
      </tr>
      <tr>
          <td>Stored procedure</td>
          <td>全支援</td>
          <td>少數支援、業務邏輯改應用層</td>
      </tr>
  </tbody>
</table>
<p>ORM 兼容性是 time-sensitive claim — JPA / Hibernate / SQLAlchemy 在 Spanner PostgreSQL dialect 上的行為隨 dialect 版本演進、實作前查最新 vendor docs。讀者要把 ORM 兼容測試放 Phase 0、不能假設「PostgreSQL ORM 直接搬到 Spanner」。</p>
<h3 id="data-topology-diff">Data topology diff</h3>
<ul>
<li>Single primary（write）+ read replica → multi-region voting + read-only replica</li>
<li>Primary key 設計：避免單調遞增（SERIAL）造成 hot split、改 UUID 或 bit-reversed</li>
<li>Partition：PostgreSQL declarative partition → Spanner 不需要顯式 partition（自動 split）</li>
</ul>
<h3 id="sizing--cost-diff第-7-規格面">Sizing / cost diff（第 7 規格面）</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Cloud SQL</th>
          <th>Spanner</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>計費單位</td>
          <td>instance class（vCPU / RAM）+ storage IOPS + HA add-on</td>
          <td>100 processing units 起跳 ≈ 1 node</td>
      </tr>
      <tr>
          <td>起跳成本</td>
          <td>小型 instance 月成本可控（小型 HA $50-200/月）</td>
          <td>100 pu × per-pu monthly rate、月成本是 Cloud SQL 小型 HA 的數倍</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>獨立計費（GB / month）</td>
          <td>含在 node count 內、無單獨 storage charge</td>
      </tr>
      <tr>
          <td>Throughput cap</td>
          <td>隨 instance class</td>
          <td>隨 pu 線性擴展</td>
      </tr>
      <tr>
          <td>跨 region replication</td>
          <td>額外 read replica cost</td>
          <td>含在 multi-region instance config 內</td>
      </tr>
      <tr>
          <td>Egress</td>
          <td>跨 region 額外</td>
          <td>跨 region 額外</td>
      </tr>
  </tbody>
</table>
<p>觸發 sizing audit 的時機：workload 行數、QPS、跨 region 需求都明確後、把「Cloud SQL HA monthly bill」對「Spanner 100 pu × monthly rate + egress」做 cost crossover 分析、無法 cost crossover 證明 → 不升。</p>
<p>Cost crossover 不是「Spanner 成本必須低於 Cloud SQL」、是「Spanner 多付的成本要對應到具體 benefit」：</p>
<ul>
<li>若 benefit 是 multi-region write residency、Spanner 多付的 cost 換得跨 region 一致性 — 對齊</li>
<li>若 benefit 只是「更新的技術」、Spanner 多付的 cost 沒對應產品價值 — 不升</li>
</ul>
<h3 id="type-判定">Type 判定</h3>
<p><strong>Type E（paradigm shift）</strong>、不是 drop-in。schema / app / operation / data topology / cost 五軸都動、不能用 Type B（drop-in）思路規劃 phase。詳細 type 判定方法看 <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>。</p>
<h2 id="phase-plan9-段每段有驗證門檻">Phase Plan：9 段、每段有驗證門檻</h2>
<h3 id="phase-0--compatibility-audit--sizing-audit">Phase 0 — Compatibility audit + sizing audit</h3>
<p>跑 schema-converter（pgloader / Spanner migration tool）、列出 incompatible feature、決定哪些改 schema、哪些改 app。hot key 風險評估（SERIAL primary key、單調遞增 timestamp）。</p>
<p>同時跑 sizing audit：</p>
<ul>
<li>估 target Spanner pu 數（基於 QPS、storage size、cross-region replication factor）</li>
<li>做 Cloud SQL HA cost vs Spanner cost crossover 分析</li>
<li>若 cost crossover 證明不出來 → halt migration、回到 driver 段重審</li>
</ul>
<p>Phase 0 是 migration 的決策閘門 — 不過閘門就停、不浪費 Phase 1+ 的 engineering effort。</p>
<h3 id="phase-1--target-schema-design">Phase 1 — Target schema design</h3>
<ul>
<li>interleaved table 設計（base on Phase 0 access pattern audit）</li>
<li>Index 重寫（GIN / GiST 用 STORING column 替代、其他用 B-tree）</li>
<li>Primary key 反序（避免 hot split）</li>
<li>Storing column 選擇（trade-off：query latency vs index size）</li>
</ul>
<p>Output 是 target DDL、跟原 PostgreSQL schema 並排 diff 文件、給 application 團隊審。</p>
<h3 id="phase-2--application-dual-target-preparation">Phase 2 — Application dual-target preparation</h3>
<ul>
<li>抽象 DB layer（repository pattern、避免直接呼 SQL）</li>
<li>SDK 並存（go-pg + Spanner client）</li>
<li>Feature flag 控制讀寫路徑（read-from-pg / read-from-spanner / dual-write）</li>
<li>Transaction 模式 audit（長交易拆短）</li>
</ul>
<h3 id="phase-3--bulk-initial-load">Phase 3 — Bulk initial load</h3>
<p>Cloud SQL → Cloud Storage（CSV / Avro）→ Dataflow → Spanner。Row count + checksum 驗證、column-level diff sample。</p>
<h3 id="phase-4--cdc-catch-up">Phase 4 — CDC catch-up</h3>
<p>Datastream from Cloud SQL → Dataflow → Spanner。Replication lag &lt; 1s 為前進門檻、sustained 24h。</p>
<h3 id="phase-5--shadow-read">Phase 5 — Shadow read</h3>
<p>Production read 同時打 Cloud SQL 跟 Spanner、diff log 異常。至少 7 天觀察、divergence rate &lt; 0.1%、p99 latency Spanner &lt; 1.5x Cloud SQL。</p>
<h3 id="phase-6--dual-write">Phase 6 — Dual write</h3>
<p>Cloud SQL 為 source-of-truth、Spanner 為 mirror。偵測 dual write divergence、評估是否提早升 source-of-truth。</p>
<h3 id="phase-7--cutover">Phase 7 — Cutover</h3>
<p>read-only window（&lt; 5 min）→ 最後 catch-up → switch source-of-truth → cutover application write。</p>
<h3 id="phase-8--cleanup">Phase 8 — Cleanup</h3>
<p>退役 Cloud SQL primary、保留 backup、清 PgBouncer / Patroni / 監控 dashboard。</p>
<h3 id="stage-0-variant-規劃">Stage 0 variant 規劃</h3>
<p>若 read-only window 不可接受（24/7 不能停機的金融 / 醫療系統）、Phase 6 dual write 期間做 conflict resolution（last-writer-wins + manual reconcile）、進入 fail-forward 模式、不走 read-only cutover。</p>
<h2 id="evidence每階段驗證材料">Evidence：每階段驗證材料</h2>
<table>
  <thead>
      <tr>
          <th>Phase</th>
          <th>Evidence</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Phase 0</td>
          <td>incompatible feature 清單、預估改動 SP、hot key 風險 row count、<strong>sizing audit 報告</strong>（target pu 數估算 + Cloud SQL HA vs Spanner cost crossover 月 / 年成本對比）</td>
      </tr>
      <tr>
          <td>Phase 1</td>
          <td>DDL diff report、預估 backfill 時間（基於 row count + Spanner 文件）</td>
      </tr>
      <tr>
          <td>Phase 3</td>
          <td>row count 對齊、column-level checksum、payload sample diff</td>
      </tr>
      <tr>
          <td>Phase 4</td>
          <td>CDC lag &lt; 1s sustained 24h、error rate &lt; 0.01%</td>
      </tr>
      <tr>
          <td>Phase 5</td>
          <td>shadow read divergence rate &lt; 0.1%、p99 latency Spanner &lt; 1.5x Cloud SQL</td>
      </tr>
      <tr>
          <td>Phase 6</td>
          <td>dual write divergence &lt; 0.01%、reconcile queue 不積壓</td>
      </tr>
      <tr>
          <td>Phase 7</td>
          <td>cutover window 內 write 一致性、回到 Phase 6 的條件（rollback path）</td>
      </tr>
  </tbody>
</table>
<p><strong>Cost crossover 報告</strong>（Phase 0 必交付）：</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">Item                          | Cloud SQL HA | Spanner 100 pu | Delta
</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">Compute monthly               | $X           | $Y             | $Y-X
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Storage monthly               | $A           | (included)     | -$A
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Cross-region replication      | $B           | (included)     | -$B
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Egress (est)                  | $C           | $C             | $0
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Total monthly                 | $X+A+B+C     | $Y+C           | $Y-X-A-B
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Annual                        | 12*above     | 12*above       | -
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Benefit (qualitative)         | -            | multi-region write residency / external consistency | -
</span></span><span class="line"><span class="ln">10</span><span class="cl">Crossover verdict             | -            | proceed / halt | -</span></span></code></pre></div><p>Verdict = <code>proceed</code> 才進 Phase 1；<code>halt</code> → 回到 Driver 段重審 driver 是否成立。</p>
<p>所有 evidence 進 incident decision log、回 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a>。</p>
<h2 id="cutover決策與-rollback">Cutover：決策與 rollback</h2>
<h3 id="cutover-window">Cutover window</h3>
<p>選用戶最低流量時段、&lt; 5 min read-only freeze、預先通知。受監管產業（對照 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a>）要算合規 lead time、每市場各自審。</p>
<h3 id="decision-owner">Decision owner</h3>
<p>DB lead + product lead + on-call SRE 共同 sign-off。受監管產業多加合規 owner。</p>
<h3 id="rollback-condition">Rollback condition</h3>
<ul>
<li>cutover 後 30 min 內 p99 write latency 持續 &gt; SLA 2x → rollback</li>
<li>error rate &gt; 1% sustained 5 min → rollback</li>
<li>對帳系統發現 divergence &gt; 0.1% → rollback</li>
</ul>
<h3 id="rollback-機制">Rollback 機制</h3>
<p>保留 Cloud SQL 為 read-only mirror 14 天、Spanner 改 read-only、reverse CDC（Spanner → Cloud SQL）需事先準備。Reverse CDC 在 Phase 4-6 期間就要 dry-run 過、不能 cutover 才第一次試。</p>
<p>連結 <a href="/blog/backend/knowledge-cards/rollback-window/" data-link-title="Rollback Window" data-link-desc="說明變更進入 production 後還能用哪種方式回退或改路線的時間與條件">rollback-window</a>、<a href="/blog/backend/knowledge-cards/rollback-condition/" data-link-title="Rollback Condition" data-link-desc="說明決策執行後出現哪些訊號時要撤回、回退或改路線">rollback-condition</a>。</p>
<h2 id="cleanup退役清單跟保留責任">Cleanup：退役清單跟保留責任</h2>
<h3 id="退役清單">退役清單</h3>
<ul>
<li>Cloud SQL primary instance</li>
<li>PgBouncer 配置</li>
<li>Patroni cluster</li>
<li>pgBackRest backup job（保留歸檔 90 天、依產業合規）</li>
<li>Datastream pipeline</li>
<li>Dataflow job</li>
</ul>
<h3 id="監控清理">監控清理</h3>
<p>postgres-specific dashboard（exporter / wal lag / autovacuum）改成 Spanner dashboard（commit_latencies / clock_skew_ms / cpu_utilization_by_priority）。</p>
<h3 id="文件--runbook-更新">文件 / runbook 更新</h3>
<p>postgres operation runbook 標記 deprecated、Spanner runbook 上線。新 runbook 含：</p>
<ul>
<li>DDL long-running operation 監控</li>
<li>TrueTime ε 異常處理</li>
<li>Cross-region instance failover drill</li>
<li>Cost monitoring alert</li>
</ul>
<h3 id="稽核--合規">稽核 / 合規</h3>
<p>保留 final pg_dump 7 年（依產業）、incident write-back 完成、合規市場各自留檔（對照 Standard Chartered case 的 per-市場合規 lead time）。</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>：app 對 timestamp 假設審計（Phase 2 必讀）</li>
<li><a href="../schema-migration-interleaved-tables/">schema-migration-interleaved-tables</a>：Phase 1 target schema 設計</li>
<li><a href="../consistency-models-comparison/">consistency-models-comparison</a>：Phase 0 應用層一致性要求釐清、Driver 段 latency no-go 的物理硬限</li>
</ul>
<h3 id="跟其他-migration-對照">跟其他 migration 對照</h3>
<ul>
<li><a href="/blog/backend/01-database/vendors/postgresql/migrate-to-aurora-dsql/" data-link-title="PostgreSQL → Aurora DSQL Migration：PG wire-compatible Distributed SQL 的 Paradigm Shift" data-link-desc="Aurora DSQL（2024-12 re:Invent preview / 2025-05 GA）是 AWS 推的 PG wire-compatible *active-active distributed SQL*、跟 self-managed PG / Aurora PG 不同 paradigm（OCC &#43; snapshot isolation &#43; multi-region strong consistency）。Migration 結構是 *protocol drop-in &#43; paradigm shift*：app SQL 不太改、但 transaction retry / extension 缺位 / 多 region 一致性需重設計。本文走 DSQL vs Aurora PG vs self-managed PG 三軸對比、為什麼遷的三條 driver（global write / operational zero-touch / region resiliency）、Type E phased plan、5 production 踩雷（transaction retry 沒處理 / extension 缺位 / sequence throughput 限制 / Aurora PG 直升 DSQL 不可行 / region failover semantic）、跟 PG → Aurora 跟 PG → CockroachDB 對比">PostgreSQL → Aurora DSQL Migration</a>：兩者都是 PostgreSQL → distributed SQL paradigm shift、選 GCP / AWS 看生態</li>
<li><a href="/blog/backend/01-database/large-scale-db-migration/" data-link-title="1.12 大規模 DB 遷移實戰" data-link-desc="跨 DB 遷移的 dual-write、[shadow read](/backend/knowledge-cards/shadow-read/)、cutover、rollback 流程 — 從實戰案例提煉的工程做法">1.12 大規模 DB 遷移實戰</a>：通用大規模遷移方法論</li>
</ul>
<h3 id="跟-case-對照">跟 case 對照</h3>
<ul>
<li><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>：dogfood case、揭露 Spanner 設計目標、不是 customer-facing capacity reference</li>
<li><a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered Aurora banking</a>：受監管產業遷移要算合規 lead time、per-市場容量規劃</li>
</ul>
<h3 id="anti-recommendation">Anti-recommendation</h3>
<p>讀者讀完本文應該能判斷：</p>
<ul>
<li>若 driver 只是「想用新技術」→ 回 Cloud SQL</li>
<li>若 workload 小（QPS &lt; 1000、行數 &lt; 數百萬）→ Cloud SQL HA 更划算</li>
<li>若應用層延遲容忍 &lt; 50ms write → Cloud SQL 單 region</li>
<li>若 cost crossover 證明不出來 → halt migration、不升</li>
</ul>
<p>Driver 是真正跨 region write residency / external consistency 對帳契約 / 單 primary 容量天花板 → 才升。Migration playbook 的目標不是把所有 Cloud SQL workload 升到 Spanner、是把「適合升」的部分用低風險路徑遷過去。</p>
]]></content:encoded></item></channel></rss>