<?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>Baas on Tarragon</title><link>https://tarrragon.github.io/blog/tags/baas/</link><description>Recent content in Baas on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 24 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/baas/index.xml" rel="self" type="application/rss+xml"/><item><title>部署光譜：從 BaaS 到自架的四條路徑</title><link>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/deployment-spectrum/</link><pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/deployment-spectrum/</guid><description>&lt;p>監控方案的選擇不是「完全自架 Go collector」和「買 Sentry 訂閱」的二元決策。中間存在兩條路徑 — 用 BaaS（Supabase / Firebase）搭出託管版 collector，或用 PaaS（Railway / Fly.io）跑自架 collector 原始碼但不管 server。四條路徑的本質差異在「哪些層自己管、哪些交給平台」。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/self-hosted-vs-commercial/" data-link-title="自架 vs 商業的判斷決策表" data-link-desc="使用者數、網路範圍、功能需求、合規要求四個維度判斷該自架還是用商業方案">自架 vs 商業的判斷決策表&lt;/a>用四個維度（使用者數 / 網路範圍 / 功能需求 / 合規）做二元分流。本章把光譜展開成四條路徑，讓中間的 BaaS 和 PaaS 選項浮現。Backend 選型模組已建立了完整的交付形態光譜（&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">交付形態選型&lt;/a>）和逐能力判斷外包深度的框架（&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">能力級買 vs 建&lt;/a>）。本章把那個框架特化到監控場景。&lt;/p>
&lt;h2 id="四條路徑">四條路徑&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>路徑&lt;/th>
 &lt;th>代表方案&lt;/th>
 &lt;th>Collector 是什麼&lt;/th>
 &lt;th>Storage 是什麼&lt;/th>
 &lt;th>自己管什麼&lt;/th>
 &lt;th>平台管什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>A. 商業監控 SaaS&lt;/td>
 &lt;td>Sentry / Datadog / Firebase Analytics&lt;/td>
 &lt;td>vendor 提供&lt;/td>
 &lt;td>vendor 提供&lt;/td>
 &lt;td>SDK 埋點&lt;/td>
 &lt;td>全部&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B. BaaS + Serverless&lt;/td>
 &lt;td>Supabase + Vercel / Cloudflare Workers&lt;/td>
 &lt;td>serverless function（自己寫）&lt;/td>
 &lt;td>managed PostgreSQL（Supabase）&lt;/td>
 &lt;td>collector 邏輯、schema&lt;/td>
 &lt;td>server 維運、DB 維運、TLS、HA&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>C. PaaS&lt;/td>
 &lt;td>Railway / Fly.io / Render&lt;/td>
 &lt;td>Go binary（自架 collector 原始碼）&lt;/td>
 &lt;td>SQLite（同 binary）或 managed DB&lt;/td>
 &lt;td>collector 邏輯、storage&lt;/td>
 &lt;td>server 維運、TLS、deploy&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>D. 完全自架&lt;/td>
 &lt;td>VPS + Go binary&lt;/td>
 &lt;td>Go binary&lt;/td>
 &lt;td>SQLite 或自管 PostgreSQL&lt;/td>
 &lt;td>全部&lt;/td>
 &lt;td>無&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>路徑 A 和 D 分別是光譜的兩端 — &lt;a href="https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/sentry-deep-dive/" data-link-title="Sentry 深入" data-link-desc="Error tracking &amp;#43; performance monitoring &amp;#43; session replay 的架構 — Sentry 從 error-first 出發如何擴展到全面可觀測性">Sentry 深入&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/firebase-suite/" data-link-title="Firebase 套件" data-link-desc="Crashlytics &amp;#43; Analytics &amp;#43; Remote Config 的整合 — Firebase 把 error tracking 和行為分析拆成獨立產品的設計取捨">Firebase 套件&lt;/a>和&lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">模組四 Collector 設計&lt;/a>已完整討論。以下展開路徑 B 和 C。&lt;/p></description><content:encoded><![CDATA[<p>監控方案的選擇不是「完全自架 Go collector」和「買 Sentry 訂閱」的二元決策。中間存在兩條路徑 — 用 BaaS（Supabase / Firebase）搭出託管版 collector，或用 PaaS（Railway / Fly.io）跑自架 collector 原始碼但不管 server。四條路徑的本質差異在「哪些層自己管、哪些交給平台」。</p>
<p><a href="/blog/monitoring/06-commercial-comparison/self-hosted-vs-commercial/" data-link-title="自架 vs 商業的判斷決策表" data-link-desc="使用者數、網路範圍、功能需求、合規要求四個維度判斷該自架還是用商業方案">自架 vs 商業的判斷決策表</a>用四個維度（使用者數 / 網路範圍 / 功能需求 / 合規）做二元分流。本章把光譜展開成四條路徑，讓中間的 BaaS 和 PaaS 選項浮現。Backend 選型模組已建立了完整的交付形態光譜（<a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">交付形態選型</a>）和逐能力判斷外包深度的框架（<a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">能力級買 vs 建</a>）。本章把那個框架特化到監控場景。</p>
<h2 id="四條路徑">四條路徑</h2>
<table>
  <thead>
      <tr>
          <th>路徑</th>
          <th>代表方案</th>
          <th>Collector 是什麼</th>
          <th>Storage 是什麼</th>
          <th>自己管什麼</th>
          <th>平台管什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A. 商業監控 SaaS</td>
          <td>Sentry / Datadog / Firebase Analytics</td>
          <td>vendor 提供</td>
          <td>vendor 提供</td>
          <td>SDK 埋點</td>
          <td>全部</td>
      </tr>
      <tr>
          <td>B. BaaS + Serverless</td>
          <td>Supabase + Vercel / Cloudflare Workers</td>
          <td>serverless function（自己寫）</td>
          <td>managed PostgreSQL（Supabase）</td>
          <td>collector 邏輯、schema</td>
          <td>server 維運、DB 維運、TLS、HA</td>
      </tr>
      <tr>
          <td>C. PaaS</td>
          <td>Railway / Fly.io / Render</td>
          <td>Go binary（自架 collector 原始碼）</td>
          <td>SQLite（同 binary）或 managed DB</td>
          <td>collector 邏輯、storage</td>
          <td>server 維運、TLS、deploy</td>
      </tr>
      <tr>
          <td>D. 完全自架</td>
          <td>VPS + Go binary</td>
          <td>Go binary</td>
          <td>SQLite 或自管 PostgreSQL</td>
          <td>全部</td>
          <td>無</td>
      </tr>
  </tbody>
</table>
<p>路徑 A 和 D 分別是光譜的兩端 — <a href="/blog/monitoring/06-commercial-comparison/sentry-deep-dive/" data-link-title="Sentry 深入" data-link-desc="Error tracking &#43; performance monitoring &#43; session replay 的架構 — Sentry 從 error-first 出發如何擴展到全面可觀測性">Sentry 深入</a>、<a href="/blog/monitoring/06-commercial-comparison/firebase-suite/" data-link-title="Firebase 套件" data-link-desc="Crashlytics &#43; Analytics &#43; Remote Config 的整合 — Firebase 把 error tracking 和行為分析拆成獨立產品的設計取捨">Firebase 套件</a>和<a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">模組四 Collector 設計</a>已完整討論。以下展開路徑 B 和 C。</p>
<h2 id="路徑-bbaas--serverless">路徑 B：BaaS + Serverless</h2>
<p>APP 上線初期用 Supabase + Vercel（或 Cloudflare Workers）搭監控後端：serverless function 接收 SDK 送來的事件、驗證 schema 後寫入 Supabase 的 PostgreSQL。整條鏈路在免費方案額度內可以零成本運作。</p>
<h3 id="架構差異">架構差異</h3>
<p>Serverless function 沒有常駐 process。模組四假設的 Go single binary 架構 — channel 背壓、single-writer goroutine pattern、in-memory buffer — 在 serverless 環境都不適用。每個 HTTP request 是獨立的 function invocation，沒有跨 request 的記憶體狀態。</p>
<p>背壓機制需要重新設計：Go collector 用 channel 容量做背壓（channel 滿回 429），serverless 版改用 DB-level 的 rate limit（PostgreSQL 的 advisory lock 或外部 rate limiter 如 Upstash Redis）或 platform-level 的 quota（Vercel 的 concurrency limit）。SDK 端的 429 處理邏輯不需要改 — 不管背壓訊號來自 channel 還是 DB quota，SDK 都是收到 429 後降採樣。</p>
<p>Downsample 和 purge 在 Go collector 是 background goroutine 定期執行。Serverless 沒有 background job — 需要外部 cron trigger（Vercel Cron / Supabase pg_cron / GitHub Actions scheduled workflow）。</p>
<h3 id="免費方案限額">免費方案限額</h3>
<p>以下為 2026-06 查詢的各平台免費方案限額。平台定價會變動，決策前以官方定價頁為準。</p>
<table>
  <thead>
      <tr>
          <th>平台</th>
          <th>免費方案限額</th>
          <th>對監控場景的意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Supabase Free</td>
          <td>500MB DB、50K MAU、500K Edge Function invocations/月</td>
          <td>500MB 約 50-100 萬筆事件（每筆 ~500 bytes）、自用場景可用數月</td>
      </tr>
      <tr>
          <td>Vercel Hobby</td>
          <td>100GB bandwidth、10s function timeout、無明確 invocation 上限</td>
          <td>瓶頸在 bandwidth 和 execution duration、非 invocation 數；timeout 對 ingestion 足夠</td>
      </tr>
      <tr>
          <td>Cloudflare Workers</td>
          <td>100K requests/天（免費）、D1 5GB</td>
          <td>100K requests/天 x 100 筆/batch = 10M events/天、D1 的 SQLite 可替代 Supabase</td>
      </tr>
  </tbody>
</table>
<p>Audit date: 2026-06。平台免費方案限額可能調整，決策前以官方定價頁為準。</p>
<h3 id="適合情境">適合情境</h3>
<p>路徑 B 適合以下組合：APP 上線初期（使用者數 &lt; 100）、團隊熟悉前端和 SQL 但不想管 server、想保留自訂 schema 和查詢彈性（商業 SaaS 的 schema 是 vendor 定義的）、零成本起步但未來可能遷到自架。</p>
<h3 id="撞牆訊號">撞牆訊號</h3>
<p>以下訊號出現時，代表路徑 B 的天花板已到、該評估遷到路徑 C 或 D：</p>
<p><strong>連線數瓶頸</strong>：Supabase Free 的 PostgreSQL 約 20 個 concurrent connection。Serverless function 每次 invocation 開新連線，高併發時可能耗盡連線池。Supabase 內建 PgBouncer 做 connection pooling 可緩解，但免費方案的 pooler 有自己的連線上限。</p>
<p><strong>Cold start 延遲</strong>：Vercel serverless function 的 cold start 約 200ms、Supabase Edge Function 約 100ms。對監控 ingestion（不是使用者面向 API）通常可接受，但如果 SDK 的 flush timeout 設得很短（&lt; 1s），cold start 可能造成偶發超時。</p>
<p><strong>Background job 限制</strong>：Downsample 和 purge 需要外部 cron。Vercel Hobby 支援最多 2 個 cron job、每個最頻繁每天觸發 1 次 — 如果需要每小時 downsample，要用 Supabase pg_cron（Free 方案支援）或外部 scheduler。</p>
<p><strong>免費額度耗盡</strong>：Supabase 的 500K Edge Function invocations/月 ≈ 每天 16K requests。如果每個 request 攢批 100 筆事件，可處理每天 160 萬筆事件。超過後進入按量付費。Vercel Hobby 無明確 invocation 上限、瓶頸在 bandwidth（100GB/月）和 execution duration。</p>
<p><strong>合規限制</strong>：Supabase Free 的 PostgreSQL 部署在特定 region。有 GDPR data residency 需求的 app（歐盟使用者的資料必須留在 EU）需確認 vendor 的 region 支援 — 免費方案的 region 選擇可能有限。</p>
<h2 id="路徑-cpaas">路徑 C：PaaS</h2>
<p>PaaS 跑的是和完全自架相同的 Go collector 原始碼，差異只在部署方式。<code>git push</code> 觸發自動 build 和 deploy，平台管 server provisioning、TLS 憑證、process supervision。Collector 的 channel 背壓、single-writer pattern、SQLite storage 全部適用 — 和本機開發環境的行為一致。</p>
<p>Railway 和 Fly.io 都支援 persistent volume — Railway Hobby 含 1GB、Fly.io Free 含 1GB（限單 region）。SQLite 的 WAL 檔案需要持久化，persistent volume 是必要條件。Render 的免費方案沒有 persistent disk — SQLite 在每次 deploy 後重置，不適合需要保留歷史事件的場景。PaaS 平台以 container 形式運行 collector，SQLite 在 container 中的 I/O 和持久化考量見 <a href="/blog/monitoring/04-collector/container-deployment/" data-link-title="Container 部署設計" data-link-desc="Docker 部署 collector 的設計 — SQLite 在 overlay filesystem 的 I/O 考量、volume mount、graceful shutdown、資源限制">Container 部署設計</a>。</p>
<p>路徑 C 適合：想用自架 collector 但不想管 server / TLS / systemd 的團隊。程式碼完全相同，遷到自架（路徑 D）的成本接近零 — 把 binary 複製到 VPS、設定 systemd service 就完成。</p>
<p>路徑 C 的天花板在平台定價 — Railway Hobby 有 $5/月的資源上限、Fly.io Free 有 3 個 shared VM。流量成長到免費額度不夠時，PaaS 的按量付費和 VPS 月租費的交叉點是遷到自架的判讀訊號。</p>
<h2 id="路徑間的遷移">路徑間的遷移</h2>
<p>遷移成本取決於起點和終點之間有多少層需要重寫。</p>
<table>
  <thead>
      <tr>
          <th>遷移方向</th>
          <th>成本</th>
          <th>主要工作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>B → C</td>
          <td>中</td>
          <td>Serverless function → Go binary（重寫 collector 邏輯）；DB 可保留或遷移</td>
      </tr>
      <tr>
          <td>B → D</td>
          <td>中</td>
          <td>同上 + 自己管 server</td>
      </tr>
      <tr>
          <td>C → D</td>
          <td>低</td>
          <td>同程式碼不同部署（複製 binary + systemd）</td>
      </tr>
      <tr>
          <td>D → C</td>
          <td>低</td>
          <td>同程式碼推到 PaaS</td>
      </tr>
      <tr>
          <td>D → A</td>
          <td>低</td>
          <td>SDK 改 endpoint 指向商業方案、不改 SDK 程式碼</td>
      </tr>
      <tr>
          <td>A → D</td>
          <td>高</td>
          <td>從零建 collector + storage + dashboard</td>
      </tr>
      <tr>
          <td>A → B</td>
          <td>高</td>
          <td>從零寫 serverless collector + 設定 managed DB</td>
      </tr>
      <tr>
          <td>A → C</td>
          <td>高</td>
          <td>從零寫 Go collector + 推到 PaaS</td>
      </tr>
  </tbody>
</table>
<p>路徑 B → C 或 B → D 的遷移代價主要在 collector 邏輯的重寫 — serverless function 的 request-level 處理和 Go binary 的 channel-based pipeline 是不同的架構，不能直接搬。資料層的遷移代價較低 — Supabase 的 PostgreSQL 資料可以用 <code>pg_dump</code> 匯出、匯入自管 PostgreSQL。</p>
<p>交付形態遷出的通用框架（資產線盤點、並行期設計、回切窗口）見 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">託管形態遷出</a>。</p>
<h2 id="外包深度對照">外包深度對照</h2>
<p>用 <a href="/blog/backend/knowledge-cards/capability-outsourcing-depth/" data-link-title="Capability Outsourcing Depth（外包深度）" data-link-desc="說明外包一塊後端能力有三種深度（managed 基礎設施、feature SaaS、BaaS bundle）、深度決定保留多少控制權與遷出代價">外包深度</a> 的三層框架（managed 基礎設施 / feature SaaS / BaaS bundle）看四條路徑：</p>
<table>
  <thead>
      <tr>
          <th>路徑</th>
          <th>外包深度</th>
          <th>控制權</th>
          <th>遷出代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A. 商業監控 SaaS</td>
          <td>feature SaaS（最深）</td>
          <td>SDK 埋點 API、vendor 定義 schema 和查詢</td>
          <td>高</td>
      </tr>
      <tr>
          <td>B. BaaS + Serverless</td>
          <td>managed 基礎設施 + 自寫 function（中間）</td>
          <td>自訂 schema、自訂查詢、自訂 collector 邏輯</td>
          <td>中</td>
      </tr>
      <tr>
          <td>C. PaaS</td>
          <td>managed 基礎設施（淺）</td>
          <td>和自架相同、只有部署平台交出去</td>
          <td>低</td>
      </tr>
      <tr>
          <td>D. 完全自架</td>
          <td>不外包</td>
          <td>完全控制</td>
          <td>無</td>
      </tr>
  </tbody>
</table>
<p>路徑 B 在外包深度上介於 managed 基礎設施和 BaaS bundle 之間 — DB 和 runtime 交給平台，但 collector 邏輯和 schema 仍由開發者控制。這和 <a href="/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">BaaS</a> 的「前端 SDK 直連平台資料庫」模式不同 — 監控場景的路徑 B 仍然有一個自己寫的中間層（serverless function），只是這個中間層跑在平台上而非自己的 server。</p>
<h2 id="選擇建議">選擇建議</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>建議路徑</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自用工具、同機或同網段</td>
          <td>D</td>
          <td>成本最低、複雜度最低</td>
      </tr>
      <tr>
          <td>APP 上線初期、使用者 &lt; 100、零成本起步</td>
          <td>B 或 A</td>
          <td>B 保留自訂彈性、A 開箱即用</td>
      </tr>
      <tr>
          <td>小型團隊、想用自架 collector 但不想管 server</td>
          <td>C</td>
          <td>程式碼相同、部署簡單、遷出成本低</td>
      </tr>
      <tr>
          <td>使用者 &gt; 1000、需要 dashboard + 告警 + replay</td>
          <td>A</td>
          <td>商業方案的功能完成度遠高於自建</td>
      </tr>
      <tr>
          <td>合規要求資料不離開自有設施</td>
          <td>D</td>
          <td>完全控制資料位置</td>
      </tr>
  </tbody>
</table>
<p>APP 上線初期選 B 或 A 取決於自訂需求 — 需要自訂 schema 和查詢邏輯（例如自定義 error fingerprint、行為事件命名規範）選 B，只需要開箱即用的 error tracking 或行為分析選 A。B 保留遷到自架的彈性（資料在自己的 PostgreSQL），A 的功能完成度更高（dashboard、告警、session replay 開箱即用）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>自架 vs 商業的詳細決策 → <a href="/blog/monitoring/06-commercial-comparison/self-hosted-vs-commercial/" data-link-title="自架 vs 商業的判斷決策表" data-link-desc="使用者數、網路範圍、功能需求、合規要求四個維度判斷該自架還是用商業方案">自架 vs 商業的判斷決策表</a></li>
<li>自架 collector 的完整設計 → <a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">模組四 Collector 設計</a></li>
<li>Backend 交付形態光譜 → <a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">交付形態選型</a></li>
<li>能力級買 vs 建判斷 → <a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">能力級買 vs 建</a></li>
<li>外包深度概念 → <a href="/blog/backend/knowledge-cards/capability-outsourcing-depth/" data-link-title="Capability Outsourcing Depth（外包深度）" data-link-desc="說明外包一塊後端能力有三種深度（managed 基礎設施、feature SaaS、BaaS bundle）、深度決定保留多少控制權與遷出代價">外包深度</a></li>
<li>BaaS 概念 → <a href="/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">BaaS</a></li>
<li>遷出劇本 → <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">託管形態遷出</a></li>
<li>Vendor lock-in 概念 → <a href="/blog/backend/knowledge-cards/vendor-lock-in/" data-link-title="Vendor Lock-In" data-link-desc="說明採用供應商產品後，其 API 與格式滲入程式碼造成的退出成本">Vendor Lock-In</a></li>
</ul>
]]></content:encoded></item><item><title>Firestore</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/firestore/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/firestore/</guid><description>&lt;p>Firestore 是 Google 的 serverless document database、承擔 mobile app 與 SPA 的正式狀態與多裝置即時同步責任。它的資料形狀是 collection 下的 document、存取模型是 client 端用 SDK 直連、授權靠 Security Rules，而不是經過自己寫的後端服務。Firestore 同時是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">Firebase&lt;/a> bundle 的資料層、也能在 Google Cloud 上單獨使用；本頁從&lt;strong>資料層 vendor 視角&lt;/strong>說明它承擔什麼狀態責任、為哪種查詢付成本、何時撞牆該遷往自建。要不要採用 BaaS 這種交付形態本身、是更上層的決策，見 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 能力級買 vs 建&lt;/a>。&lt;/p>
&lt;p>官方文件路由：&lt;a href="https://firebase.google.com/docs/firestore">Firestore documentation&lt;/a>、&lt;a href="https://firebase.google.com/docs/firestore/data-model">Firestore data model&lt;/a>、&lt;a href="https://firebase.google.com/docs/firestore/pricing">Firestore pricing&lt;/a>；本頁時間敏感的計費與限制 claim 以官方為準、最後檢查日 2026-06-16。&lt;/p>
&lt;h2 id="教學路線client-直連的-document-正式狀態">教學路線：client 直連的 document 正式狀態&lt;/h2>
&lt;p>Firestore 服務頁的教學目標是把「前端直接讀寫資料庫」這個存取模型的責任說清楚。讀者讀完後要能判斷 Firestore 何時是合適的正式狀態，何時因為查詢形狀、成本曲線或授權複雜度該轉向自建後端配 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL&lt;/a> 或留在 document model 換 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB&lt;/a>。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>學習段&lt;/th>
 &lt;th>核心問題&lt;/th>
 &lt;th>對應段落&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Client-direct state&lt;/td>
 &lt;td>前端用 SDK 直連、授權下沉到 Security Rules 後責任邊界在哪&lt;/td>
 &lt;td>定位、存取模型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Document shape&lt;/td>
 &lt;td>collection / document / subcollection 如何決定查詢能力&lt;/td>
 &lt;td>資料形狀、適用場景&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query boundary&lt;/td>
 &lt;td>為什麼跨 collection 報表查不出來、index 與查詢限制如何約束建模&lt;/td>
 &lt;td>不適用場景、常見陷阱&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Realtime / offline&lt;/td>
 &lt;td>snapshot listener 與 offline persistence 解哪類多裝置同步問題&lt;/td>
 &lt;td>適用場景、跟其他 vendor 的取捨&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>替代路由&lt;/td>
 &lt;td>撞到報表、成本或授權牆時、遷往自建 relational 或換 document vendor&lt;/td>
 &lt;td>下一步路由、遷移 playbook&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="定位serverless-document-store--baas-資料層">定位：serverless document store + BaaS 資料層&lt;/h2>
&lt;p>Firestore 跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB&lt;/a>、&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> 同屬 NoSQL document / KV 家族，但承擔的責任層級不同：&lt;/p>
&lt;ul>
&lt;li>資料組織成 collection 下的 document，document 可巢狀 subcollection，單 document 上限 1 MiB&lt;/li>
&lt;li>沒有 server 端 JOIN，跨 collection 的關聯要靠 application 多次查詢自己組、或在寫入時反正規化&lt;/li>
&lt;li>存取模型以 client SDK 直連為主，授權寫在 Security Rules（一套規則 DSL），而不是後端 API 的權限中介層&lt;/li>
&lt;li>兩種營運模式：Firestore Native mode（行動 / web、含 realtime 與 offline）與 Datastore mode（server 端、相容舊 Datastore）&lt;/li>
&lt;/ul>
&lt;p>傳統定位：Firebase 行動 app 與 SPA 的後端資料層、MVP 快速驗證期、多裝置即時同步的產品。&lt;/p></description><content:encoded><![CDATA[<p>Firestore 是 Google 的 serverless document database、承擔 mobile app 與 SPA 的正式狀態與多裝置即時同步責任。它的資料形狀是 collection 下的 document、存取模型是 client 端用 SDK 直連、授權靠 Security Rules，而不是經過自己寫的後端服務。Firestore 同時是 <a href="/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">Firebase</a> bundle 的資料層、也能在 Google Cloud 上單獨使用；本頁從<strong>資料層 vendor 視角</strong>說明它承擔什麼狀態責任、為哪種查詢付成本、何時撞牆該遷往自建。要不要採用 BaaS 這種交付形態本身、是更上層的決策，見 <a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型</a> 與 <a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 能力級買 vs 建</a>。</p>
<p>官方文件路由：<a href="https://firebase.google.com/docs/firestore">Firestore documentation</a>、<a href="https://firebase.google.com/docs/firestore/data-model">Firestore data model</a>、<a href="https://firebase.google.com/docs/firestore/pricing">Firestore pricing</a>；本頁時間敏感的計費與限制 claim 以官方為準、最後檢查日 2026-06-16。</p>
<h2 id="教學路線client-直連的-document-正式狀態">教學路線：client 直連的 document 正式狀態</h2>
<p>Firestore 服務頁的教學目標是把「前端直接讀寫資料庫」這個存取模型的責任說清楚。讀者讀完後要能判斷 Firestore 何時是合適的正式狀態，何時因為查詢形狀、成本曲線或授權複雜度該轉向自建後端配 <a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL</a> 或留在 document model 換 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB</a>。</p>
<table>
  <thead>
      <tr>
          <th>學習段</th>
          <th>核心問題</th>
          <th>對應段落</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Client-direct state</td>
          <td>前端用 SDK 直連、授權下沉到 Security Rules 後責任邊界在哪</td>
          <td>定位、存取模型</td>
      </tr>
      <tr>
          <td>Document shape</td>
          <td>collection / document / subcollection 如何決定查詢能力</td>
          <td>資料形狀、適用場景</td>
      </tr>
      <tr>
          <td>Query boundary</td>
          <td>為什麼跨 collection 報表查不出來、index 與查詢限制如何約束建模</td>
          <td>不適用場景、常見陷阱</td>
      </tr>
      <tr>
          <td>Realtime / offline</td>
          <td>snapshot listener 與 offline persistence 解哪類多裝置同步問題</td>
          <td>適用場景、跟其他 vendor 的取捨</td>
      </tr>
      <tr>
          <td>替代路由</td>
          <td>撞到報表、成本或授權牆時、遷往自建 relational 或換 document vendor</td>
          <td>下一步路由、遷移 playbook</td>
      </tr>
  </tbody>
</table>
<h2 id="定位serverless-document-store--baas-資料層">定位：serverless document store + BaaS 資料層</h2>
<p>Firestore 跟 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB</a>、<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> 同屬 NoSQL document / KV 家族，但承擔的責任層級不同：</p>
<ul>
<li>資料組織成 collection 下的 document，document 可巢狀 subcollection，單 document 上限 1 MiB</li>
<li>沒有 server 端 JOIN，跨 collection 的關聯要靠 application 多次查詢自己組、或在寫入時反正規化</li>
<li>存取模型以 client SDK 直連為主，授權寫在 Security Rules（一套規則 DSL），而不是後端 API 的權限中介層</li>
<li>兩種營運模式：Firestore Native mode（行動 / web、含 realtime 與 offline）與 Datastore mode（server 端、相容舊 Datastore）</li>
</ul>
<p>傳統定位：Firebase 行動 app 與 SPA 的後端資料層、MVP 快速驗證期、多裝置即時同步的產品。</p>
<p>資料層視角的定位：一塊 <em>managed serverless document store</em>，把 capacity、replication、failover、scaling 全部交給平台，代價是查詢能力與資料模型沿平台特性生長。</p>
<h2 id="資料形狀與查詢邊界">資料形狀與查詢邊界</h2>
<p>Firestore 為「已知路徑的 document 讀寫」付成本，不為「任意欄位的 ad-hoc 查詢」付成本。這個取向決定了它的甜蜜區與牆：</p>
<ul>
<li>單 document 與單 collection 內的 key-based / 條件查詢高效，且每筆查詢都要有對應 index（單欄 index 自動建立、複合查詢要建 composite index）</li>
<li>查詢結果集的計費與大小跟「讀了幾筆 document」成正比，不是跟「掃了多少」— 一次回 10,000 筆就計 10,000 次 read</li>
<li>缺少 server 端 aggregation pipeline 與 JOIN；跨集合報表（例如「本月各地區訂單金額」）在 Firestore 上要嘛預先把彙總寫成一份 document、要嘛把資料複製到分析系統</li>
<li>沒有原生全文搜尋，全文需求要接專門的 <a href="/blog/backend/knowledge-cards/search-index/" data-link-title="Search Index" data-link-desc="說明搜尋索引如何承擔全文檢索、排序與查詢體驗">search index</a>（Algolia、Elasticsearch / OpenSearch）</li>
</ul>
<p>這條查詢邊界是 Firestore 最容易被低估的設計約束。它不是「功能還沒做」，而是 client 直連 + serverless 計費模型的必然結果：把任意 ad-hoc 查詢開放給前端，等於把不可預測的成本與掃描壓力暴露在公網。建模時要先窮舉 access pattern、再決定 document 結構，跟 <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 正向用例">DynamoDB single-table design</a> 的 access-pattern-first 思路同源。</p>
<h2 id="一致性realtime-與容量特性">一致性、realtime 與容量特性</h2>
<p><strong>一致性</strong>：</p>
<ul>
<li>單 document 讀寫與「查詢結果在同一 region 內」提供 strong consistency</li>
<li>多 region 部署靠平台複製、跨 region 讀取可能有延遲；一致性語意由平台決定、不可調到自管資料庫那種 isolation level 顆粒</li>
</ul>
<p><strong>Realtime 與 offline</strong>：</p>
<ul>
<li>snapshot listener 讓 client 訂閱 query 結果、資料變更即時推送，是多裝置同步的核心能力</li>
<li>行動 / web SDK 內建 offline persistence，斷線時讀寫本地快取、回線後同步，這是自建 REST API 要額外工程才有的能力</li>
</ul>
<p><strong>容量與寫入熱點</strong>：</p>
<ul>
<li>serverless 自動擴縮，無 connection 概念，前端裝置數不直接轉成資料庫連線壓力</li>
<li>單一 document 的高頻寫入會撞到 contention（官方建議單 document 的持續寫入維持在每秒個位數量級、高頻計數器要用 distributed counter 分片）</li>
<li>寫入吞吐與索引維護成本綁在一起：每多一個 index、寫入就多一份維護成本</li>
</ul>
<p>容量特性的時間敏感數字（每秒寫入軟上限、單 document contention 門檻）以 <a href="https://firebase.google.com/docs/firestore/best-practices">官方 best practices</a> 為準，設計高頻寫入前先查當前限制。</p>
<h2 id="適用場景">適用場景</h2>
<p><strong>1. 行動 app / SPA 的 MVP 後端</strong>：</p>
<ul>
<li>認證接 Firebase Auth、資料存 Firestore、推播接 Cloud Messaging，整個 MVP 沒有自己的後端服務</li>
<li>對應 <a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21</a> BaaS 段的「把後端工程師這個角色延後」</li>
</ul>
<p><strong>2. 多裝置即時同步</strong>：</p>
<ul>
<li>協作筆記、聊天、即時看板這類「一處改、多處即時更新」的產品</li>
<li>snapshot listener + offline persistence 是這類需求的天然形狀</li>
</ul>
<p><strong>3. access pattern 穩定的 document 工作負載</strong>：</p>
<ul>
<li>user profile、設定、feed item、活動紀錄這類讀多寫少、查詢路徑固定的資料</li>
<li>跟 <a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">source of truth</a> 對齊：Firestore 可以是這些資料的正式狀態</li>
</ul>
<h2 id="不適用場景">不適用場景</h2>
<p><strong>1. 跨實體報表與分析查詢</strong>：</p>
<ul>
<li>跨 collection JOIN、ad-hoc 篩選、彙總統計在 Firestore 上要靠資料複製工程</li>
<li>替代：自建 relational（<a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL</a>）或把資料同步進分析系統</li>
</ul>
<p><strong>2. 成本對流量敏感的高讀取場景</strong>：</p>
<ul>
<li>計費隨 document read / write / delete 線性成長，高流量下可能超過自建</li>
<li>替代：自管資料庫 + 應用層 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">cache</a>，把熱讀取的單位成本壓下來</li>
</ul>
<p><strong>3. 複雜授權需要可測試的控制面</strong>：</p>
<ul>
<li>client 直連模型把授權全塞進 Security Rules，規則長到難以 review / 測試時，控制面風險升高</li>
<li>替代：把授權拉回後端 API 中介層（自建後端 + 任意資料庫）</li>
</ul>
<p><strong>4. 強一致的多實體交易</strong>：</p>
<ul>
<li>Firestore 有 transaction 與 batch write，但跨大量 document 的複雜交易不是它的主場</li>
<li>替代：relational database 的多表交易</li>
</ul>
<h2 id="跟其他-vendor-的取捨">跟其他 vendor 的取捨</h2>
<p><strong>vs MongoDB（document 對 document）</strong>：</p>
<ul>
<li>Firestore：serverless、client 直連、realtime listener、GCP / Firebase 綁定、查詢能力受限</li>
<li>MongoDB：查詢與 aggregation 彈性高、跨雲、要自管或用 Atlas managed、走後端中介存取</li>
<li>選 Firestore：行動 / 即時同步 / 想省整層後端</li>
<li>選 MongoDB：document model 但要彈性查詢、aggregation、跨雲可攜，見 <a href="/blog/backend/01-database/vendors/db3-vendor-selection/" data-link-title="DB3 Vendor Selection：document / KV / multi-model 三方選型 &#43; workload shape 前置判讀" data-link-desc="MongoDB / DynamoDB / Cosmos DB 三家 NoSQL 選型 entry point：workload shape × access pattern × consistency 三軸前置判讀、migration path 三型、federated DB 視角、三 vendor 對比 10 軸">db3 vendor selection</a></li>
</ul>
<p><strong>vs DynamoDB（serverless NoSQL 對 serverless NoSQL）</strong>：</p>
<ul>
<li>Firestore：GCP / Firebase 生態、內建 realtime 與 offline、client 直連為主</li>
<li>DynamoDB：AWS 生態、access-pattern-first KV、通常走後端整合、streams 接事件驅動</li>
<li>兩者的 access-pattern-first 建模思路相近，差別在生態與 client 直連的有無</li>
</ul>
<p><strong>vs SQLite（行動端的反向選擇）</strong>：</p>
<ul>
<li>Firestore：雲端 store、自動多裝置 sync、realtime</li>
<li>SQLite：embedded、offline-first、無 sync（見 <a href="/blog/backend/01-database/vendors/sqlite/" data-link-title="SQLite" data-link-desc="embedded、單檔案、test / CLI / edge 場景的標準選擇、近年因 Cloudflare D1 / Turso 等服務復興">SQLite vendor</a>）</li>
<li>選 Firestore：需要跨裝置同步與即時更新</li>
<li>選 SQLite：純單機 / offline、不需要雲端同步</li>
</ul>
<p><strong>vs Supabase（BaaS bundle 的另一條路）</strong>：</p>
<ul>
<li>Firestore：document model、Google 的 BaaS bundle 資料層</li>
<li>Supabase：底層是 PostgreSQL（relational）、開源 BaaS bundle，遷出時資料是標準 SQL</li>
<li>兩者都是 client 直連 + 規則授權的 BaaS 形狀，差別在資料模型（document vs relational）與遷出時的資料可攜性；Supabase 的資料層判讀見 <a href="/blog/backend/01-database/vendors/postgresql/managed-pg-comparison/" data-link-title="Managed PostgreSQL Comparison" data-link-desc="RDS PostgreSQL、Aurora PostgreSQL、Cloud SQL、Azure Database for PostgreSQL、Neon、Supabase、Crunchy Bridge 的責任邊界比較">Managed PostgreSQL 比較</a>，選型層錨點見 <a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22</a></li>
</ul>
<h2 id="容量規劃要點">容量規劃要點</h2>
<p><strong>1. access pattern 先於 document 結構</strong>：</p>
<ul>
<li>列出 application 對資料的所有讀寫路徑、再設計 collection / document 形狀</li>
<li>access pattern 沒想清楚就建模，後面報表查不出來要重做</li>
</ul>
<p><strong>2. 反正規化換查詢效率</strong>：</p>
<ul>
<li>為了避免跨 collection 多次查詢，常把關聯資料冗餘寫進同一 document</li>
<li>代價是寫入時要維護多份副本的一致性，對應 <a href="/blog/backend/01-database/reconciliation-data-repair/" data-link-title="1.9 Reconciliation 與 Data Repair" data-link-desc="資料不一致的分類、偵測模式、修復策略、audit trail、跟 backup / PITR 整合">1.9 Reconciliation</a></li>
</ul>
<p><strong>3. index 與寫入成本綁定</strong>：</p>
<ul>
<li>複合查詢要先建 composite index、否則查詢直接失敗</li>
<li>每個 index 增加寫入維護成本，移除用不到的 index 是容量優化的一環</li>
</ul>
<p><strong>4. 高頻寫入用 distributed counter</strong>：</p>
<ul>
<li>單一 document 撞到 contention 上限時，把計數拆成多個 shard document 再彙總</li>
</ul>
<p><strong>5. 成本以 document 數計，不以掃描量計</strong>：</p>
<ul>
<li>容量估算要算「每個畫面 / API 觸發幾次 read」、乘上日活與頻率</li>
<li>把熱讀取移到 <a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">應用層快取</a> 是壓低 read 計費的主要手段</li>
</ul>
<h2 id="常見陷阱">常見陷阱</h2>
<ul>
<li><strong>把 Firestore 當關聯式用</strong>：規劃了一堆需要 JOIN 的 collection、上線後跨集合查詢全靠 client 自己組、latency 與 read 成本爆炸</li>
<li><strong>報表需求到了才發現查不出來</strong>：老闆要月報、Firestore 沒有 aggregation pipeline、被迫臨時搭資料複製管線</li>
<li><strong>Security Rules 長到沒人敢改</strong>：授權全寫在規則 DSL、沒有版本控制與測試、變更時靠人工推敲</li>
<li><strong>單 document 當高頻計數器</strong>：直播按讚 / 即時計數寫爆單一 document 的 contention 上限</li>
<li><strong>忽略 read 計費規模</strong>：list 畫面一次回上千筆、每次重整都計上千次 read、帳單月底才浮現</li>
</ul>
<h2 id="deep-article-章節群">Deep article 章節群</h2>
<p>Firestore overview 負責第一輪服務判斷；vendor 特有機制的設定、踩坑與容量規劃拆成 deep article。下表是目前已建立的實作層教材，讀法是先讀 overview 判斷服務適配，再按撞到的壓力選 deep article。</p>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>文件</th>
          <th>教學責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>授權控制面</td>
          <td><a href="/blog/backend/01-database/vendors/firestore/security-rules-authz-modeling/" data-link-title="Firestore Security Rules 授權建模與可測試化：把規則當程式碼治理" data-link-desc="Firestore client 直連模型把整個授權控制面壓在 Security Rules 這套 DSL 裡；本文展開規則的求值模型、把授權拆成可組合 function、用 emulator 寫單元測試、五個把規則寫成資安漏洞的 production 踩坑，以及規則複雜度撞牆時把授權拉回後端的邊界">Security Rules 授權建模與可測試化</a></td>
          <td>規則求值模型、可組合 function、emulator 單元測試、把規則當程式碼治理</td>
      </tr>
      <tr>
          <td>高頻寫入</td>
          <td><a href="/blog/backend/01-database/vendors/firestore/distributed-counter-high-frequency-write/" data-link-title="Firestore 高頻寫入與 distributed counter：單 document contention 邊界與分片計數" data-link-desc="Firestore 單一 document 有持續寫入的軟上限、高頻計數寫爆 contention 是常見事故；本文展開寫入 contention 的成因、distributed counter 分片計數的實作與讀取彙總、shard 數量與讀寫成本的取捨、五個高頻寫入踩坑，以及計數需求超過分片能處理時改走外部聚合的邊界">高頻寫入與 distributed counter</a></td>
          <td>單 document contention 邊界、分片計數、shard 數與讀寫成本取捨</td>
      </tr>
      <tr>
          <td>資料建模</td>
          <td><a href="/blog/backend/01-database/vendors/firestore/denormalization-fanout-consistency/" data-link-title="Firestore document 反正規化與一致性維護：fan-out write、副本同步與資料修復" data-link-desc="Firestore 沒有 JOIN，查詢能力逼著把關聯資料反正規化複製多份；本文展開反正規化的建模決策、fan-out write 維護副本一致、batch 與 transaction 的選擇、五個副本不一致的 production 踩坑，以及反正規化複雜到該回關聯式的邊界">document 反正規化與一致性維護</a></td>
          <td>反正規化決策、fan-out write、副本同步、不一致修復</td>
      </tr>
      <tr>
          <td>即時同步</td>
          <td><a href="/blog/backend/01-database/vendors/firestore/realtime-listener-fanout-cost/" data-link-title="Firestore realtime listener 扇出與成本：snapshot 訂閱、re-read 計費與連線規模" data-link-desc="Firestore 的 snapshot listener 提供即時同步、但訂閱的扇出、查詢結果變動的 re-read 計費與連線數會在規模下變成成本與效能瓶頸；本文展開 listener 的推送模型、訂閱範圍設計、五個 realtime 成本踩坑，以及即時需求超過 listener 該換推送架構的邊界">realtime listener 扇出與成本</a></td>
          <td>snapshot 推送模型、訂閱範圍設計、re-read 計費、連線規模</td>
      </tr>
  </tbody>
</table>
<p>讀法路由：撞到資料外洩 / 越權，讀 Security Rules；撞到熱門事件寫爆計數，讀 distributed counter；改一筆要連動改一千筆，讀反正規化；即時功能帳單失控，讀 realtime listener。撞到報表 / 成本 / 授權整體性的牆，走 <a href="/blog/backend/01-database/vendors/firestore/migrate-to-relational/" data-link-title="從 Firestore 遷往自建 relational：撞牆驅動的 Type E 重建模、存取模型反轉與並行期" data-link-desc="Firestore → 自建後端 &#43; relational 不是匯資料而是反轉存取模型：client 直連變 API 中介、Security Rules 授權變後端授權、document 反正規化變正規 schema、realtime listener 與 offline 同步要重建；本文走 Type E paradigm shift 結構、展開為何字面遷移不成立、哪些該遷哪些先留、dual-write &#43; shadow read 階段化與遷出代價判讀">遷往自建 relational</a>。</p>
<h2 id="hands-on-操作演練">Hands-on 操作演練</h2>
<p>deep article 講機制判讀，<a href="/blog/backend/01-database/vendors/firestore/hands-on/" data-link-title="Firestore Hands-on 操作路線" data-link-desc="用 Firebase Emulator Suite 在本地演練 Firestore：emulator quickstart、Security Rules 單元測試、distributed counter 分片計數，全程零雲端成本、可重跑、產出可驗證 artifact">Hands-on 操作路線</a> 把機制轉成可在本地 <a href="https://firebase.google.com/docs/emulator-suite">Firebase Emulator</a> 跑的演練——零雲端成本、可重跑、產出可驗證 artifact。三個 lab：<a href="/blog/backend/01-database/vendors/firestore/hands-on/local-emulator-quickstart/" data-link-title="Firestore Local Emulator Quickstart" data-link-desc="用 Firebase CLI 啟動 Firestore emulator、寫 firestore.rules、用 admin SDK seed 資料、跑 query baseline 與 cleanup，建立後續 Security Rules 與 distributed counter lab 共用的本地環境">emulator quickstart</a>（建立共用環境）、<a href="/blog/backend/01-database/vendors/firestore/hands-on/security-rules-test-lab/" data-link-title="Firestore Security Rules Test Lab" data-link-desc="用 @firebase/rules-unit-testing 在 emulator 上把 Security Rules 寫成自動化測試：放行 / 越權拒絕 / 未登入拒絕 / 欄位竄改拒絕四類斷言、firebase emulators:exec 在 CI 跑、把規則測試接進 release gate">Security Rules test lab</a>（規則自動化測試 + 接 release gate）、<a href="/blog/backend/01-database/vendors/firestore/hands-on/distributed-counter-lab/" data-link-title="Firestore Distributed Counter Lab" data-link-desc="在 emulator 上實作 distributed counter：建立 N 個 shard、隨機分片寫入、觀察 shard 分佈是否均勻、讀取彙總驗證總和正確，並說明 contention 本身是 emulator 不模擬的 production 特性">distributed counter lab</a>（分片計數機制驗證）。lab 全程標明 emulator 驗得了什麼（功能行為、規則求值）、驗不了什麼（計費、寫入軟上限要回雲端）。</p>
<h2 id="已知-limitation-與後續路由">已知 limitation 與後續路由</h2>
<p>Firestore overview 完成服務判斷、資料形狀、查詢邊界與替代路由；deep article 章節群覆蓋授權、高頻寫入、反正規化與即時同步四個機制；hands-on 章節群提供 emulator 演練。後續可補的方向：offline persistence 的衝突解決深入、realtime listener 在雲端的成本量測 lab（emulator 不計費、要在雲端 staging 跑）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>完整 T1 對照：<a href="/blog/backend/01-database/vendors/" data-link-title="資料庫 Vendor 清單" data-link-desc="規劃 SQL、managed SQL、document、KV 與 distributed SQL 的服務頁撰寫順序與教學大綱">01-database vendors index</a></li>
<li>同類對比：<a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor</a>（彈性查詢 document）/ <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 vendor</a>（access-pattern-first KV）/ <a href="/blog/backend/01-database/vendors/db3-vendor-selection/" data-link-title="DB3 Vendor Selection：document / KV / multi-model 三方選型 &#43; workload shape 前置判讀" data-link-desc="MongoDB / DynamoDB / Cosmos DB 三家 NoSQL 選型 entry point：workload shape × access pattern × consistency 三軸前置判讀、migration path 三型、federated DB 視角、三 vendor 對比 10 軸">db3 vendor selection</a>（document / KV / multi-model 三方選型）</li>
<li>遷出方向：<a href="/blog/backend/01-database/vendors/firestore/migrate-to-relational/" data-link-title="從 Firestore 遷往自建 relational：撞牆驅動的 Type E 重建模、存取模型反轉與並行期" data-link-desc="Firestore → 自建後端 &#43; relational 不是匯資料而是反轉存取模型：client 直連變 API 中介、Security Rules 授權變後端授權、document 反正規化變正規 schema、realtime listener 與 offline 同步要重建；本文走 Type E paradigm shift 結構、展開為何字面遷移不成立、哪些該遷哪些先留、dual-write &#43; shadow read 階段化與遷出代價判讀">Firestore → 自建 relational</a>（撞到報表 / 成本 / 授權牆後的 Type E 重建模 playbook）</li>
<li>操作演練：<a href="/blog/backend/01-database/vendors/firestore/hands-on/" data-link-title="Firestore Hands-on 操作路線" data-link-desc="用 Firebase Emulator Suite 在本地演練 Firestore：emulator quickstart、Security Rules 單元測試、distributed counter 分片計數，全程零雲端成本、可重跑、產出可驗證 artifact">Firestore Hands-on</a>（emulator quickstart、Security Rules 測試、distributed counter lab）</li>
<li>容量背景：<a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">1.10 KV / Document DB 容量規劃</a></li>
<li>選型上層：<a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型</a> / <a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 能力級買 vs 建</a> / <a href="/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">BaaS 知識卡</a></li>
<li>從託管平台遷出的資產線盤點：<a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出</a></li>
<li>官方：<a href="https://firebase.google.com/docs/firestore">Firestore documentation</a>、<a href="https://firebase.google.com/docs/firestore/best-practices">Firestore best practices</a>、<a href="https://firebase.google.com/docs/firestore/pricing">Firestore pricing</a></li>
</ul>
]]></content:encoded></item><item><title>從 Firestore 遷往自建 relational：撞牆驅動的 Type E 重建模、存取模型反轉與並行期</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/firestore/migrate-to-relational/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/firestore/migrate-to-relational/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/firestore/" data-link-title="Firestore" data-link-desc="Firebase / Google Cloud 的 serverless document database、collection / document 模型、client 直連 &amp;#43; Security Rules、realtime listener 與 offline 同步、BaaS bundle 的資料層面">Firestore&lt;/a> overview 的 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 寫作方法論&lt;/a>。BaaS 託管平台整場遷出的資產線盤點與並行期總覽見 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出&lt;/a>；本文聚焦資料層的跨 paradigm 重建模。&lt;/p>&lt;/blockquote>
&lt;p>「我們把 Firestore 整包匯出，匯進 PostgreSQL 就好。」這句話低估了遷移的真正內容 — Firestore 遷往自建 relational 的難點是&lt;strong>反轉整個存取模型&lt;/strong>，搬資料只是其中最容易的一條線。Firestore 是 client 用 SDK 直連資料庫、授權寫在 Security Rules；自建 relational 是 client 打自己的後端 API、授權在後端中介層。資料可以匯出，但反正規化的 document 形狀、沿查詢限制長出來的資料模型、realtime listener 與 offline 同步能力，都沒有 1:1 的對應物。字面意義的「匯出再匯入」只搬走了最容易的那部分。本文走 paradigm shift 結構：先講為何字面遷移不成立、再講哪些該遷哪些先留、最後才是階段化執行。&lt;/p>
&lt;h2 id="遷移的-driver三面牆不是relational-比較好">遷移的 driver：三面牆，不是「relational 比較好」&lt;/h2>
&lt;p>Firestore 遷往自建很少因為「relational 比較好」這種空泛動機，而是撞到 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21&lt;/a> BaaS 段描述的三面具體的牆。先確認 driver 真的成立、再啟動遷移：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Driver&lt;/th>
 &lt;th>撞牆訊號&lt;/th>
 &lt;th>遷移要解的問題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>報表 / 分析查詢&lt;/td>
 &lt;td>跨 collection 報表查不出來、已經在維護資料複製管線&lt;/td>
 &lt;td>把資料放回支援 JOIN / aggregation 的 relational&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本曲線轉折&lt;/td>
 &lt;td>read / write 計費隨流量線性成長、超過自建 + cache 的成本&lt;/td>
 &lt;td>用自管資料庫 + 應用層快取壓低單位成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>授權控制面失控&lt;/td>
 &lt;td>Security Rules 長到難以測試 / review、授權邏輯沒有版本治理&lt;/td>
 &lt;td>把授權拉回後端 API 中介層、可測試可審查&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>&lt;strong>No-go condition&lt;/strong>：產品仍以多裝置 realtime 同步與 offline-first 為核心賣點、且查詢需求簡單、成本仍在舒適區 → 先不要遷。這些正是 Firestore 的主場，硬遷會把 realtime / offline 這層平台白送的能力變成自己要重建的工程。遷移前先問「撞的是哪面牆」，三面牆都沒撞到就是 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22&lt;/a> 講的偽自建。&lt;/p>&lt;/blockquote>
&lt;p>逐能力遷出是常態而非整包搬離：&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 的「成長期 SaaS」例子&lt;/a> 就是只把撞牆的資料層搬到自管 PostgreSQL、認證留在原平台。本文預設的也是這種逐能力遷出 — 遷的是資料層，不一定連認證、儲存一起搬。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/firestore/" data-link-title="Firestore" data-link-desc="Firebase / Google Cloud 的 serverless document database、collection / document 模型、client 直連 &#43; Security Rules、realtime listener 與 offline 同步、BaaS bundle 的資料層面">Firestore</a> overview 的 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 寫作方法論</a>。BaaS 託管平台整場遷出的資產線盤點與並行期總覽見 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出</a>；本文聚焦資料層的跨 paradigm 重建模。</p></blockquote>
<p>「我們把 Firestore 整包匯出，匯進 PostgreSQL 就好。」這句話低估了遷移的真正內容 — Firestore 遷往自建 relational 的難點是<strong>反轉整個存取模型</strong>，搬資料只是其中最容易的一條線。Firestore 是 client 用 SDK 直連資料庫、授權寫在 Security Rules；自建 relational 是 client 打自己的後端 API、授權在後端中介層。資料可以匯出，但反正規化的 document 形狀、沿查詢限制長出來的資料模型、realtime listener 與 offline 同步能力，都沒有 1:1 的對應物。字面意義的「匯出再匯入」只搬走了最容易的那部分。本文走 paradigm shift 結構：先講為何字面遷移不成立、再講哪些該遷哪些先留、最後才是階段化執行。</p>
<h2 id="遷移的-driver三面牆不是relational-比較好">遷移的 driver：三面牆，不是「relational 比較好」</h2>
<p>Firestore 遷往自建很少因為「relational 比較好」這種空泛動機，而是撞到 <a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21</a> BaaS 段描述的三面具體的牆。先確認 driver 真的成立、再啟動遷移：</p>
<table>
  <thead>
      <tr>
          <th>Driver</th>
          <th>撞牆訊號</th>
          <th>遷移要解的問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>報表 / 分析查詢</td>
          <td>跨 collection 報表查不出來、已經在維護資料複製管線</td>
          <td>把資料放回支援 JOIN / aggregation 的 relational</td>
      </tr>
      <tr>
          <td>成本曲線轉折</td>
          <td>read / write 計費隨流量線性成長、超過自建 + cache 的成本</td>
          <td>用自管資料庫 + 應用層快取壓低單位成本</td>
      </tr>
      <tr>
          <td>授權控制面失控</td>
          <td>Security Rules 長到難以測試 / review、授權邏輯沒有版本治理</td>
          <td>把授權拉回後端 API 中介層、可測試可審查</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>No-go condition</strong>：產品仍以多裝置 realtime 同步與 offline-first 為核心賣點、且查詢需求簡單、成本仍在舒適區 → 先不要遷。這些正是 Firestore 的主場，硬遷會把 realtime / offline 這層平台白送的能力變成自己要重建的工程。遷移前先問「撞的是哪面牆」，三面牆都沒撞到就是 <a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22</a> 講的偽自建。</p></blockquote>
<p>逐能力遷出是常態而非整包搬離：<a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 的「成長期 SaaS」例子</a> 就是只把撞牆的資料層搬到自管 PostgreSQL、認證留在原平台。本文預設的也是這種逐能力遷出 — 遷的是資料層，不一定連認證、儲存一起搬。</p>
<h2 id="6-維-diff-audit主導維度是-paradigm--application-change">6 維 diff audit：主導維度是 paradigm + application change</h2>
<p>遷移前先盤點 source 跟 target 的差異落在哪幾維、決定 playbook 結構：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Firestore → 自建 relational</th>
          <th>程度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema / API</td>
          <td>document / collection → 正規 table、SDK query → 後端 API + SQL</td>
          <td>High</td>
      </tr>
      <tr>
          <td>Operational model</td>
          <td>serverless 全託管 → 自管 / managed 資料庫、自己擔 backup / failover</td>
          <td>High</td>
      </tr>
      <tr>
          <td>Paradigm</td>
          <td>client 直連 + 規則授權 → API 中介 + 後端授權</td>
          <td>High</td>
      </tr>
      <tr>
          <td>Components 數量</td>
          <td>單一平台 → 新增一層自建後端服務 + 資料庫</td>
          <td>High</td>
      </tr>
      <tr>
          <td>Application change</td>
          <td>前端拔 SDK 改打 API、realtime / offline 要重建</td>
          <td>High</td>
      </tr>
      <tr>
          <td>Data topology</td>
          <td>平台複製 → 自己設計 replica / 多 region / DR</td>
          <td>Medium</td>
      </tr>
  </tbody>
</table>
<p>主導維度是 <strong>paradigm 與 application change</strong>：六維裡五維落在 High。這定義了結構 — <strong>Type E paradigm shift</strong>（排除 schema 翻譯 Type A 和 drop-in Type B）：存取模型反轉、部分能力重建、可能長期混合（資料層自建、認證仍留平台）。</p>
<h2 id="為什麼字面遷移不成立存取模型反轉">為什麼字面遷移不成立：存取模型反轉</h2>
<p>Firestore 的存取模型是 <em>前端即客戶端、資料庫直接面向公網、授權在規則層</em>；自建 relational 是 <em>前端打後端、後端面向資料庫、授權在服務層</em>。這個反轉是遷移的核心難點，不在資料搬運。</p>
<p><strong>反正規化 document → 正規 schema</strong>：</p>
<ul>
<li>Firestore 為了繞開查詢限制，常把關聯資料冗餘寫進同一 document（一份資料複製多處）</li>
<li>遷往 relational 要把冗餘拆回正規化 table、重建外鍵關係，這是逆向工程：要先讀懂當初為什麼這樣存</li>
<li>反過來說，有些 document 的巢狀結構在 relational 用 JSONB 保留更省事（見 <a href="/blog/backend/01-database/vendors/postgresql/jsonb-deep-dive/" data-link-title="PostgreSQL JSONB Deep Dive：Binary Storage &#43; GIN Index 為什麼是結構性優勢" data-link-desc="PG JSONB（9.4&#43;）是 *binary 儲存的 JSON*、可直接 GIN index、是 PG 在 JSON workload 的結構性優勢、跟 MongoDB / MySQL 8.0 JSON_TABLE 比仍領先。本文走 JSON vs JSONB 差異、GIN index 機制（jsonb_ops vs jsonb_path_ops）、operator &#43; path query、partial JSONB indexing、5 production 踩雷（大 JSONB 跟 TOAST / nested update / index 選錯 op class / jsonb_path_query 跟 jsonb_path_exists 行為差 / partial index 條件搞錯）、何時用 JSONB vs 拆 column">PostgreSQL jsonb</a>）— 不是所有 document 都要拆成 table</li>
</ul>
<p><strong>Security Rules 授權 → 後端授權</strong>：</p>
<ul>
<li>Firestore 的授權邏輯散在 Security Rules DSL 裡，遷移要把每一條規則翻譯成後端 API 的權限檢查</li>
<li>這層翻譯是安全敏感的：漏一條規則等於開一個越權查詢的洞，對應 <a href="/blog/backend/01-database/red-team-data-layer/" data-link-title="1.5 攻擊者視角（紅隊）：資料層弱點判讀" data-link-desc="從資料存取邊界、外洩路徑與修復代價、盤點 database 的主要弱點">1.5 資料層紅隊</a></li>
</ul>
<p><strong>SDK 直連 → API 中介</strong>：</p>
<ul>
<li>前端原本用 Firestore SDK 直接讀寫，遷移後要拔掉 SDK、改打自建 API</li>
<li>這是 application 層的大改，不是資料庫換連線字串</li>
</ul>
<p><strong>realtime listener / offline persistence → 自己重建</strong>：</p>
<ul>
<li>snapshot listener 的即時推送、offline 讀寫快取，是平台白送的能力</li>
<li>自建要用 WebSocket / SSE 重建即時層（見 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列</a> 與 presence 設計）、用前端本地儲存重建 offline — 這是遷移最容易被漏估的工作量</li>
</ul>
<p>所以遷移的第一步不是匯資料，是<strong>盤點 application 對 Firestore 的所有依賴面</strong>：查詢路徑、授權規則、realtime 訂閱、offline 行為。這份清單決定哪些能直接遷、哪些要重建、哪些先留在平台。</p>
<h2 id="哪些該遷哪些先留逐能力混合">哪些該遷、哪些先留（逐能力混合）</h2>
<p>Type E 的本質是不收斂 — 不必把所有 Firebase 能力一次搬完。判讀標準：</p>
<table>
  <thead>
      <tr>
          <th>Workload / 能力特徵</th>
          <th>去向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>需要報表 / JOIN / aggregation 的資料</td>
          <td>遷自建 relational</td>
      </tr>
      <tr>
          <td>讀取量大、成本敏感、access pattern 穩定的資料</td>
          <td>遷自建 + <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">應用層快取</a></td>
      </tr>
      <tr>
          <td>仍以 realtime 同步為核心、查詢簡單的資料</td>
          <td>先留 Firestore / 或最後再遷</td>
      </tr>
      <tr>
          <td>認證（Firebase Auth）</td>
          <td>可留平台、逐能力決定（見 0.22）</td>
      </tr>
      <tr>
          <td>檔案儲存（Firebase Storage）</td>
          <td>可留平台、與資料層解耦後再評估</td>
      </tr>
  </tbody>
</table>
<p><a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 的成長期 SaaS</a> 是這個判讀的 case anchor：撞牆的是資料層的 query 複雜度與成本，遷的就是資料層，認證留在原地。混合不是過渡失敗，是逐能力選型的穩態。</p>
<h2 id="phase-plan存取模型反轉的階段化">Phase plan：存取模型反轉的階段化</h2>
<p>paradigm shift 的階段化把不可逆動作放到最後、每階段有獨立驗證門檻：</p>
<h4 id="phase-1依賴面盤點">Phase 1：依賴面盤點</h4>
<p>列出 application 對 Firestore 的所有讀寫路徑、Security Rules 授權條件、realtime 訂閱點、offline 行為。標每項的頻率、安全敏感度、是否可重建。這份清單不完整不進下一階段。</p>
<h4 id="phase-2relational-重建模">Phase 2：relational 重建模</h4>
<p>把反正規化 document 設計回正規 schema、決定哪些巢狀結構用 JSONB 保留。同步設計後端 API 的端點與授權檢查、把 Security Rules 逐條翻譯成服務層權限。對應 <a href="/blog/backend/01-database/schema-design/" data-link-title="1.2 Schema Design 與資料建模" data-link-desc="整理 table、index、key、partition、denormalization 與命名規則">1.2 schema design</a> 與 <a href="/blog/backend/01-database/red-team-data-layer/" data-link-title="1.5 攻擊者視角（紅隊）：資料層弱點判讀" data-link-desc="從資料存取邊界、外洩路徑與修復代價、盤點 database 的主要弱點">1.5 資料層紅隊</a>。</p>
<h4 id="phase-3自建後端--dual-write">Phase 3：自建後端 + dual-write</h4>
<p>立起自建後端 API 與資料庫，前端關鍵寫入路徑同時寫 Firestore 與新後端。Firestore 仍是 source of truth、新庫累積資料。dual-write 要處理一邊失敗的補償（對應 <a href="/blog/backend/01-database/reconciliation-data-repair/" data-link-title="1.9 Reconciliation 與 Data Repair" data-link-desc="資料不一致的分類、偵測模式、修復策略、audit trail、跟 backup / PITR 整合">1.9 Reconciliation</a>）。</p>
<h4 id="phase-4backfill-歷史資料">Phase 4：backfill 歷史資料</h4>
<p>把 Firestore 既有 document 按新 schema 轉換寫入新庫。backfill 與 dual-write 並行時要處理覆蓋順序，backfill 不能蓋掉 dual-write 的新值。轉換過程記 checksum / row count 對照。</p>
<h4 id="phase-5shadow-read-驗證">Phase 5：shadow read 驗證</h4>
<p>讀路徑同時打 Firestore 與新後端、比對結果、記錄差異但仍以 Firestore 回應用戶。差異率降到可接受才進 cutover。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 Schema Migration Rollout 證據</a> 的 evidence 方法。</p>
<h4 id="phase-6漸進-cutover--重建即時層">Phase 6：漸進 cutover + 重建即時層</h4>
<p>前端逐步把讀寫從 Firestore SDK 切到自建 API（按比例 / 按功能模組），保留切回能力。若產品需要 realtime，這階段要把 snapshot listener 換成自建即時層（WebSocket / SSE）並驗證延遲與斷線重連。cutover 完成後資料層的 source of truth 轉到自建；未遷的能力（認證、儲存）仍在平台 — 混合架構成立。</p>
<h2 id="evidence每階段的前進依據">Evidence：每階段的前進依據</h2>
<p>每個階段用資料證明可前進、不靠感覺：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>Evidence</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>dual-write</td>
          <td>雙寫成功率、寫入失敗補償紀錄、兩邊 document / row 數差異</td>
      </tr>
      <tr>
          <td>backfill</td>
          <td>已轉換比例、轉換錯誤數、checksum 對照、反正規化還原正確性抽查</td>
      </tr>
      <tr>
          <td>shadow read</td>
          <td>新舊結果差異率、差異分類（建模差異 vs 真錯誤）、授權翻譯漏洞掃描</td>
      </tr>
      <tr>
          <td>cutover</td>
          <td>切流比例、新 API latency p99、error rate、realtime 推送延遲、rollback 是否觸發</td>
      </tr>
  </tbody>
</table>
<p>這些 evidence 對齊 <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>（Source / Time range / Query link / Owner / Data quality）與 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>。授權翻譯這項要特別當成 gate 條件 — 它是安全邊界、不只是功能正確性。</p>
<h2 id="cutover-與-rollback-決策">Cutover 與 rollback 決策</h2>
<p>資料庫切流失敗代價高、加上這裡牽涉授權正確性，決策權責要寫清楚：</p>
<ul>
<li><strong>cutover window</strong>：選低流量時段、明確切流比例階梯（如 1% → 10% → 50% → 100%），按功能模組切比按全站切安全</li>
<li><strong>rollback condition</strong>：新 API error rate / latency 超閾值、shadow read 差異率異常、或發現授權翻譯漏洞 → 切回 Firestore</li>
<li><strong>decision owner</strong>：誰有權喊停、依據什麼 evidence、記錄在 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 incident decision log</a></li>
<li><strong>realtime 連續性</strong>：若即時層同步切換，要驗證切換期間訂閱不中斷、或明確告知短暫降級</li>
</ul>
<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>
<p>Type E 的 cleanup 通常不是「關掉整個 Firebase」— 多數情況認證、儲存仍留平台：</p>
<ul>
<li>已遷資料路徑的 Firestore collection、Security Rules、dual-write code path 退役</li>
<li>shadow read 比對 code 移除</li>
<li>前端殘留的 Firestore SDK 依賴清掉（資料層已不走它）</li>
<li>但 Firebase Auth / Storage 若仍在用，保留；明確標示哪條資料路徑的 source of truth 是自建庫、哪條仍在平台</li>
<li>Firestore 的資料匯出備份保留到確認新庫穩定，對應 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3</a> 的並行期退役判準</li>
</ul>
<p>混合架構不是遷移失敗、是逐能力選型的穩態 — 撞牆的資料層自建、沒撞牆的認證 / 儲存留在平台。</p>
<h2 id="失敗模式">失敗模式</h2>
<p>production 常見的 5 個踩雷：</p>
<h4 id="case-1只匯資料漏了存取模型反轉">Case 1：只匯資料、漏了存取模型反轉</h4>
<p>把 Firestore 匯出匯進 PostgreSQL 就以為遷完、忘了前端還在打 SDK、授權還在 Security Rules。修法：依賴面盤點是 Phase 1、資料搬運只是其中一條線，存取模型反轉才是主體。</p>
<h4 id="case-2security-rules-翻譯漏洞">Case 2：Security Rules 翻譯漏洞</h4>
<p>把規則翻成後端授權時漏一條、開了越權查詢的洞、上線後資料外洩。修法：授權翻譯要逐條對照 + 紅隊驗證（<a href="/blog/backend/01-database/red-team-data-layer/" data-link-title="1.5 攻擊者視角（紅隊）：資料層弱點判讀" data-link-desc="從資料存取邊界、外洩路徑與修復代價、盤點 database 的主要弱點">1.5</a>）、當成 cutover gate 條件、不是功能 bug。</p>
<h4 id="case-3反正規化還原錯誤">Case 3：反正規化還原錯誤</h4>
<p>document 的冗餘副本拆回 table 時還原錯關係、新庫資料關聯接錯。修法：Phase 2 先讀懂當初為何反正規化、backfill 後抽查還原正確性、shadow read 比對抓出建模差異。</p>
<h4 id="case-4低估-realtime--offline-重建工作量">Case 4：低估 realtime / offline 重建工作量</h4>
<p>以為遷資料庫就好、上線才發現 snapshot listener 與 offline 同步整層要自己重建、進度爆炸。修法：依賴面盤點就把 realtime 訂閱點與 offline 行為標出來、列入工作量、必要時這層最後遷或先保留。</p>
<h4 id="case-5dual-write-一邊失敗沒補償">Case 5：dual-write 一邊失敗沒補償</h4>
<p>dual-write 時新庫寫成功 Firestore 失敗（或反之）、兩邊分歧、cutover 後資料不完整。修法：dual-write 要有失敗補償（記錄、重試、標記人工對帳），對應 <a href="/blog/backend/01-database/reconciliation-data-repair/" data-link-title="1.9 Reconciliation 與 Data Repair" data-link-desc="資料不一致的分類、偵測模式、修復策略、audit trail、跟 backup / PITR 整合">1.9 Reconciliation</a>。</p>
<p><strong>Anti-recommendation</strong>：產品仍重度依賴 realtime / offline、或團隊還沒有自建後端與資料庫的營運能力（backup、failover、授權設計）→ 先不要遷。可先把一塊撞牆最明顯、realtime 需求最低的資料（例如報表來源資料）試點、累積自建營運經驗再擴大。</p>
<h2 id="容量與成本crossover-判讀">容量與成本：crossover 判讀</h2>
<p>遷移的成本判讀關鍵是 <em>遷移後的總帳</em>、不是只看 Firestore 帳單：</p>
<ul>
<li><strong>遷移當下</strong>：高 read 流量下，自管資料庫 + 應用層快取的單位成本常低於 Firestore 的 per-read 計費</li>
<li><strong>但要加回自建的隱性成本</strong>：後端服務的開發與維運、資料庫的 backup / failover / 擴容、realtime 層的重建與維護、團隊人力</li>
<li><strong>判讀分層</strong>：撞到成本牆且已有後端團隊 → 自建總帳通常划算；仍是小團隊、realtime 是核心、流量不大 → Firestore 的「平台白送能力」可能仍比自建總帳便宜</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：crossover 隨流量形狀、region pricing、團隊成本結構變動、無通用閾值。遷移省下的 Firestore 帳單要扣掉自建後端 + 資料庫 + 即時層的維運成本後再比，不是直接拿兩邊資料庫帳單對照。</p></blockquote>
<p>接回 <a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本、風險與選型取捨</a>、<a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">1.10 KV / Document DB 容量規劃</a>。</p>
<h2 id="邊界與整合">邊界與整合</h2>
<h3 id="跟其他遷移路徑的關係">跟其他遷移路徑的關係</h3>
<ul>
<li><strong>保留 document model</strong>：若只是要逃離 Firestore 的查詢限制、但 document 形狀仍適合，遷 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB</a> 比遷 relational 的 paradigm 跨度小、不必反正規化還原</li>
<li><strong>整包託管遷出</strong>：若連認證、儲存一起搬離 Firebase，整場資產線盤點與並行期走 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出</a>、本文是其中資料層那一條</li>
<li><strong>反向視角</strong>：哪些資料當初就不該進 Firestore（報表來源、強一致交易），見 <a href="/blog/backend/01-database/vendors/firestore/#%e4%b8%8d%e9%81%a9%e7%94%a8%e5%a0%b4%e6%99%af" data-link-title="Firestore" data-link-desc="Firebase / Google Cloud 的 serverless document database、collection / document 模型、client 直連 &#43; Security Rules、realtime listener 與 offline 同步、BaaS bundle 的資料層面">Firestore overview 的不適用場景</a></li>
</ul>
<h3 id="sibling-與-cross-link">Sibling 與 cross-link</h3>
<ul>
<li><a href="/blog/backend/01-database/vendors/firestore/" data-link-title="Firestore" data-link-desc="Firebase / Google Cloud 的 serverless document database、collection / document 模型、client 直連 &#43; Security Rules、realtime listener 與 offline 同步、BaaS bundle 的資料層面">Firestore overview</a> — 服務定位與查詢邊界</li>
<li><a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6 資料庫轉換實作</a> — 通用 dual-write / shadow read / cutover 框架</li>
<li><a href="/blog/backend/01-database/red-team-data-layer/" data-link-title="1.5 攻擊者視角（紅隊）：資料層弱點判讀" data-link-desc="從資料存取邊界、外洩路徑與修復代價、盤點 database 的主要弱點">1.5 資料層紅隊</a> — Security Rules 授權翻譯的安全驗證</li>
<li><a href="/blog/backend/01-database/reconciliation-data-repair/" data-link-title="1.9 Reconciliation 與 Data Repair" data-link-desc="資料不一致的分類、偵測模式、修復策略、audit trail、跟 backup / PITR 整合">1.9 Reconciliation 與 Data Repair</a> — dual-write 失敗補償與資料對帳</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/migrate-rds-mongodb-to-dynamodb/" data-link-title="從 RDS / MongoDB 遷移到 DynamoDB：access-pattern-first 重建模、混合架構與 cost crossover" data-link-desc="RDS / MongoDB → DynamoDB 不是搬 schema 而是換 paradigm；本文走 Type E paradigm shift 結構，展開為何字面遷移不成立、access pattern 重建模、哪些 workload 該遷哪些該留的混合架構、dual-write &#43; shadow read 階段化，以及 Zomato cost crossover 的長期成本判讀">從 RDS / MongoDB 遷往 DynamoDB</a> — 同為 Type E paradigm shift 的對照（方向相反：遷入 NoSQL vs 遷出 BaaS）</li>
<li><a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型</a> / <a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 能力級買 vs 建</a> — 遷移 driver 的選型層背景</li>
</ul>
]]></content:encoded></item><item><title>0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型</title><link>https://tarrragon.github.io/blog/backend/00-service-selection/capability-buy-vs-build/</link><pubDate>Sun, 14 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/00-service-selection/capability-buy-vs-build/</guid><description>&lt;p>能力級買 vs 建的核心原則是：交付形態 gate 決定整個系統要不要自建之後、每一塊非核心能力仍各自是一次獨立的買 vs 建決策。&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型&lt;/a> 把「整包該不該自建」篩過一輪、留下決定自建核心的團隊；但自建核心不等於每塊能力都要自己寫 — 認證、搜尋、金流、Email、表單蒐集、檔案儲存、後台操作介面這些非差異化能力、預設先問「能不能買現成的」。決策單位是能力、不是系統；真實系統是逐能力混搭、核心自建、周邊外包。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、讀者能夠：&lt;/p>
&lt;ol>
&lt;li>把「買 vs 建」的判斷單位從整個系統下降到單一能力&lt;/li>
&lt;li>辨識三種外包深度（managed 基礎設施、feature SaaS、BaaS bundle）與 no-code 到 dev-tool 的服務光譜&lt;/li>
&lt;li>用 commodity、合規、長尾成本與團隊規模判斷一塊能力該買還是該建&lt;/li>
&lt;li>算清外包的隱性帳：整合接縫、鎖定、遷出代價、以及權重如何隨情境浮動&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="判讀先確認該不該讀這章">【判讀】先確認該不該讀這章&lt;/h2>
&lt;p>本章預設讀者已經過了 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21&lt;/a> 的 whole-system gate、決定自建核心。在那之前有一種讀者該先被擋下來：&lt;strong>非工程師、目的是解自己的生活痛點或做第一個 MVP 的人&lt;/strong>。對這種讀者、0.21 已經把答案給完了 — 用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">BaaS&lt;/a>（Supabase、Firebase）就是對的起點、本章的逐能力拆解反而是過度工程。免費額度對個人專案通常夠用、BaaS 連後端與資料庫的串接、效能調教、資源調配一起省掉、把這些當成「之後真的撞牆再說」的問題、是這個尺度最誠實的選擇。&lt;/p>
&lt;p>常見的誤判是把選型問題問錯層。「我該選什麼資料庫」這個提問、對非工程師多半真正的答案在 0.21（這個尺度根本不必自己管資料庫）、不在本章。下表把提問者的身分對應到該回的章節：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>提問者情境&lt;/th>
 &lt;th>真正該回的章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>非工程師、解個人痛點、第一個 MVP&lt;/td>
 &lt;td>0.21 — 用 BaaS、本章不必細讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>已決定自建核心、要逐塊判斷哪些能力外包&lt;/td>
 &lt;td>本章&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>已長期自建、某塊外包能力撐不住要搬回自建&lt;/td>
 &lt;td>本章 §「什麼訊號指向『自建或搬離』」+ &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管遷出&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>第一列展開說明：判斷自己是不是這種讀者、看「撞牆之後誰來修」。個人專案撞到 Supabase 免費額度上限時、升級付費方案或匯出資料換平台都是幾小時的事、不需要先把架構拆乾淨。把工程資源花在「現在還沒發生的擴展問題」上、是把 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本、風險與選型取捨&lt;/a> 講的機會成本花在錯的地方。確定自己是要自建核心、且周邊有多塊能力要逐一決定買或建、再往下讀。&lt;/p>
&lt;h2 id="判讀三種外包深度與-no-code-到-dev-tool-光譜">【判讀】三種外包深度與 no-code 到 dev-tool 光譜&lt;/h2>
&lt;p>外包一塊能力有深度之分、不是非黑即白的二元動作（見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/capability-outsourcing-depth/" data-link-title="Capability Outsourcing Depth（外包深度）" data-link-desc="說明外包一塊後端能力有三種深度（managed 基礎設施、feature SaaS、BaaS bundle）、深度決定保留多少控制權與遷出代價">Capability Outsourcing Depth&lt;/a> 卡片）。同樣是「不自己寫」、把維運交出去跟把整塊能力連業務邏輯一起交出去、控制權與遷出代價差一個量級。下面這三層講的是把能力交給雲端託管側時、交出多少 — 自架 OSS 或買 on-prem 授權、只租控制平面的自管形態是另一條平行軸（鎖定點在運維 know-how 與授權、不在 vendor API）、不收在這三層裡。下表把三種深度分開、每種附代表服務與遷出代價：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>外包深度&lt;/th>
 &lt;th>你外包什麼、還擁有什麼&lt;/th>
 &lt;th>dev-tool 代表&lt;/th>
 &lt;th>no-code / low-code 代表&lt;/th>
 &lt;th>遷出代價&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>managed 基礎設施&lt;/td>
 &lt;td>外包維運、仍擁有 schema、query 與架構&lt;/td>
 &lt;td>Aurora、ElastiCache、Neon&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>低–中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>feature SaaS（能力即服務）&lt;/td>
 &lt;td>外包整塊能力與內部業務邏輯、只消費 API&lt;/td>
 &lt;td>Auth0、Algolia、Stripe、SendGrid&lt;/td>
 &lt;td>Ragic、SurveyCake、Airtable、Typeform&lt;/td>
 &lt;td>中–高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨能力 BaaS bundle&lt;/td>
 &lt;td>一個 vendor 一次給多塊能力&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>Supabase、Firebase、Amplify&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>managed 基礎設施是最淺的外包：vendor 接手備份、修補、failover、擴容、跨區複製、但 schema、query、index、資料模型還是你的。遷出時資料是標準格式、架構是自己畫的、換一家 managed PostgreSQL 主要是搬資料與改連線字串。撞牆時你改得動的邊界很寬 — 慢查詢自己優化、index 自己加、只有底層硬體與維運動作在 vendor 手上。不過「邊界很寬」有前提：受限的 serverless 或內嵌型 managed（沒給 superuser、裝不了 extension 的那種）邊界其實更窄、選之前要確認需要的控制權它給不給。&lt;/p>
&lt;p>feature SaaS 把整塊能力連同它的內部業務邏輯一起交出去、你消費的是一組 API 而不是一台機器。買 Auth0 不只是省下跑一台認證伺服器、是把「密碼雜湊策略、MFA、SSO、social login、session 管理」整套交給 vendor、你只接它的 SDK。代價是你改得動的邊界縮到 vendor 開放的擴展點為止 — 它沒給的客製、你做不到、繞過去就是在 vendor 之外再搭一層。遷出代價中到高、因為資料模型與業務規則都沿 vendor 的特性長出來。&lt;/p>
&lt;p>選 feature SaaS 真正在賭的、是它的擴展點夠不夠你長出差異化。commodity 能力的多數需求跟同業一樣、買現成就解決；會區分產品的是少數、而那少數得疊在 vendor 之上自己長 — 但要先確認那塊差異化存不存在：有些 commodity（收個款、寄封信）差異化趨近零、整塊就是純買、這條擴展點判準根本不啟動。判準只在「確實有一塊差異化要疊上去」時才是選型核心。能不能疊、看 vendor 把控制權開到哪 — 開 API 讓你改它的排序、規則、把自己的資料接進它的邏輯、差異化長得出來；只開一面設定面板、核心邏輯動不了、一撞到差異化需求就得繞到 vendor 外另建一塊補。所以選的時候除了問「它做不做這塊能力」、更關鍵的是「它讓不讓我在上面長出獨有的那部分」 — 這一題決定它能陪產品走多遠。&lt;/p></description><content:encoded><![CDATA[<p>能力級買 vs 建的核心原則是：交付形態 gate 決定整個系統要不要自建之後、每一塊非核心能力仍各自是一次獨立的買 vs 建決策。<a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型</a> 把「整包該不該自建」篩過一輪、留下決定自建核心的團隊；但自建核心不等於每塊能力都要自己寫 — 認證、搜尋、金流、Email、表單蒐集、檔案儲存、後台操作介面這些非差異化能力、預設先問「能不能買現成的」。決策單位是能力、不是系統；真實系統是逐能力混搭、核心自建、周邊外包。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、讀者能夠：</p>
<ol>
<li>把「買 vs 建」的判斷單位從整個系統下降到單一能力</li>
<li>辨識三種外包深度（managed 基礎設施、feature SaaS、BaaS bundle）與 no-code 到 dev-tool 的服務光譜</li>
<li>用 commodity、合規、長尾成本與團隊規模判斷一塊能力該買還是該建</li>
<li>算清外包的隱性帳：整合接縫、鎖定、遷出代價、以及權重如何隨情境浮動</li>
</ol>
<hr>
<h2 id="判讀先確認該不該讀這章">【判讀】先確認該不該讀這章</h2>
<p>本章預設讀者已經過了 <a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21</a> 的 whole-system gate、決定自建核心。在那之前有一種讀者該先被擋下來：<strong>非工程師、目的是解自己的生活痛點或做第一個 MVP 的人</strong>。對這種讀者、0.21 已經把答案給完了 — 用 <a href="/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">BaaS</a>（Supabase、Firebase）就是對的起點、本章的逐能力拆解反而是過度工程。免費額度對個人專案通常夠用、BaaS 連後端與資料庫的串接、效能調教、資源調配一起省掉、把這些當成「之後真的撞牆再說」的問題、是這個尺度最誠實的選擇。</p>
<p>常見的誤判是把選型問題問錯層。「我該選什麼資料庫」這個提問、對非工程師多半真正的答案在 0.21（這個尺度根本不必自己管資料庫）、不在本章。下表把提問者的身分對應到該回的章節：</p>
<table>
  <thead>
      <tr>
          <th>提問者情境</th>
          <th>真正該回的章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>非工程師、解個人痛點、第一個 MVP</td>
          <td>0.21 — 用 BaaS、本章不必細讀</td>
      </tr>
      <tr>
          <td>已決定自建核心、要逐塊判斷哪些能力外包</td>
          <td>本章</td>
      </tr>
      <tr>
          <td>已長期自建、某塊外包能力撐不住要搬回自建</td>
          <td>本章 §「什麼訊號指向『自建或搬離』」+ <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管遷出</a></td>
      </tr>
  </tbody>
</table>
<p>第一列展開說明：判斷自己是不是這種讀者、看「撞牆之後誰來修」。個人專案撞到 Supabase 免費額度上限時、升級付費方案或匯出資料換平台都是幾小時的事、不需要先把架構拆乾淨。把工程資源花在「現在還沒發生的擴展問題」上、是把 <a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本、風險與選型取捨</a> 講的機會成本花在錯的地方。確定自己是要自建核心、且周邊有多塊能力要逐一決定買或建、再往下讀。</p>
<h2 id="判讀三種外包深度與-no-code-到-dev-tool-光譜">【判讀】三種外包深度與 no-code 到 dev-tool 光譜</h2>
<p>外包一塊能力有深度之分、不是非黑即白的二元動作（見 <a href="/blog/backend/knowledge-cards/capability-outsourcing-depth/" data-link-title="Capability Outsourcing Depth（外包深度）" data-link-desc="說明外包一塊後端能力有三種深度（managed 基礎設施、feature SaaS、BaaS bundle）、深度決定保留多少控制權與遷出代價">Capability Outsourcing Depth</a> 卡片）。同樣是「不自己寫」、把維運交出去跟把整塊能力連業務邏輯一起交出去、控制權與遷出代價差一個量級。下面這三層講的是把能力交給雲端託管側時、交出多少 — 自架 OSS 或買 on-prem 授權、只租控制平面的自管形態是另一條平行軸（鎖定點在運維 know-how 與授權、不在 vendor API）、不收在這三層裡。下表把三種深度分開、每種附代表服務與遷出代價：</p>
<table>
  <thead>
      <tr>
          <th>外包深度</th>
          <th>你外包什麼、還擁有什麼</th>
          <th>dev-tool 代表</th>
          <th>no-code / low-code 代表</th>
          <th>遷出代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>managed 基礎設施</td>
          <td>外包維運、仍擁有 schema、query 與架構</td>
          <td>Aurora、ElastiCache、Neon</td>
          <td>—</td>
          <td>低–中</td>
      </tr>
      <tr>
          <td>feature SaaS（能力即服務）</td>
          <td>外包整塊能力與內部業務邏輯、只消費 API</td>
          <td>Auth0、Algolia、Stripe、SendGrid</td>
          <td>Ragic、SurveyCake、Airtable、Typeform</td>
          <td>中–高</td>
      </tr>
      <tr>
          <td>跨能力 BaaS bundle</td>
          <td>一個 vendor 一次給多塊能力</td>
          <td>—</td>
          <td>Supabase、Firebase、Amplify</td>
          <td>高</td>
      </tr>
  </tbody>
</table>
<p>managed 基礎設施是最淺的外包：vendor 接手備份、修補、failover、擴容、跨區複製、但 schema、query、index、資料模型還是你的。遷出時資料是標準格式、架構是自己畫的、換一家 managed PostgreSQL 主要是搬資料與改連線字串。撞牆時你改得動的邊界很寬 — 慢查詢自己優化、index 自己加、只有底層硬體與維運動作在 vendor 手上。不過「邊界很寬」有前提：受限的 serverless 或內嵌型 managed（沒給 superuser、裝不了 extension 的那種）邊界其實更窄、選之前要確認需要的控制權它給不給。</p>
<p>feature SaaS 把整塊能力連同它的內部業務邏輯一起交出去、你消費的是一組 API 而不是一台機器。買 Auth0 不只是省下跑一台認證伺服器、是把「密碼雜湊策略、MFA、SSO、social login、session 管理」整套交給 vendor、你只接它的 SDK。代價是你改得動的邊界縮到 vendor 開放的擴展點為止 — 它沒給的客製、你做不到、繞過去就是在 vendor 之外再搭一層。遷出代價中到高、因為資料模型與業務規則都沿 vendor 的特性長出來。</p>
<p>選 feature SaaS 真正在賭的、是它的擴展點夠不夠你長出差異化。commodity 能力的多數需求跟同業一樣、買現成就解決；會區分產品的是少數、而那少數得疊在 vendor 之上自己長 — 但要先確認那塊差異化存不存在：有些 commodity（收個款、寄封信）差異化趨近零、整塊就是純買、這條擴展點判準根本不啟動。判準只在「確實有一塊差異化要疊上去」時才是選型核心。能不能疊、看 vendor 把控制權開到哪 — 開 API 讓你改它的排序、規則、把自己的資料接進它的邏輯、差異化長得出來；只開一面設定面板、核心邏輯動不了、一撞到差異化需求就得繞到 vendor 外另建一塊補。所以選的時候除了問「它做不做這塊能力」、更關鍵的是「它讓不讓我在上面長出獨有的那部分」 — 這一題決定它能陪產品走多遠。</p>
<p>跨能力 BaaS bundle 是一個 vendor 同時提供多塊能力、用整合當賣點。它的遷出代價最高、來自多塊能力被同一套整合綁在一起、不在任何單一能力 —— 例如同一組登入身分同時管資料庫、檔案與即時訂閱的權限、搬走其中一塊就要拆開這層共用整合（見下方 bundle 專段）。</p>
<p>這三種深度橫切一條 no-code 到 dev-tool 的光譜、而光譜的兩端服務不同的人。feature SaaS 這一層特別明顯：Auth0、Algolia 是 dev-tool、要寫 code 接 API、客製空間大、但需要工程整合能力；Ragic、SurveyCake、Airtable、Typeform 是 no-code、連 schema 都不必碰、填表就能用、客製天花板通常較低、但起步門檻也低到非工程師能獨立操作。選哪一端不只看「需要哪塊能力」、更看「誰來維護它」。一個沒有工程隊的小團隊、把會員資料放 Ragic、滿意度調查放 SurveyCake、是把維護權留在能自己改的人手上；同樣的能力換成 Auth0 + 自建問卷服務、每次調整都回到工程隊列、對這種團隊反而更貴。</p>
<h2 id="觀察什麼訊號指向買這塊能力">【觀察】什麼訊號指向「買這塊能力」</h2>
<p>一塊能力該優先評估外包、訊號集中在「自建不產生競爭優勢、卻要承擔沒有上限的長尾成本」。下表列可觀察訊號與它指向的判斷：</p>
<table>
  <thead>
      <tr>
          <th>可觀察訊號</th>
          <th>指向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>這塊能力同業要的都一樣、做得再好也不構成差異化</td>
          <td>commodity 能力、預設先買</td>
      </tr>
      <tr>
          <td>合規負擔重且標準化（金流的 PCI、認證的 SSO / SOC2）</td>
          <td>把合規面交給專做這件事的 vendor</td>
      </tr>
      <tr>
          <td>自建後維護成本沒有上限（投遞率、反詐欺、登入方式矩陣）</td>
          <td>長尾成本型能力、買掉長尾</td>
      </tr>
      <tr>
          <td>團隊缺這個領域的專才、或時間壓力不允許自建</td>
          <td>用 SaaS 換時間、把人力留給核心</td>
      </tr>
  </tbody>
</table>
<p>commodity 這一列是最常見的買訊號。認證、Email 投遞、金流處理、問卷蒐集、物件儲存 / 檔案 / CDN、後台操作介面（internal tooling）都落在這裡 — 每個產品要的功能幾乎一樣、自己寫一套不會讓產品更有競爭力。後台介面值得特別點出：很多團隊把「完整後台可操作」當成自建理由、但 admin panel 本身是 commodity、Supabase Studio、Retool、Appsmith 這類工具讓你連著資料庫就生出可操作的後台、把工程時間留給真正客製的業務流程。</p>
<p>自己架一台 SMTP 寄 email 看起來簡單、真正的成本藏在 deliverability — SPF、DKIM、DMARC、IP 信譽、退信處理、進垃圾桶的排查、是一條沒有終點的維護線、而 SendGrid 這類服務把這條線變成它的本業。這就是長尾成本最容易被低估的地方：金流的反詐欺、認證的 MFA 與 social login 矩陣同理 — 第一版很快、長期維護吃掉的人力沒有上限。</p>
<h2 id="觀察什麼訊號指向自建或搬離">【觀察】什麼訊號指向「自建或搬離」</h2>
<p>外包不是預設終點、有四種訊號會把一塊能力從「買」翻回「建」。這一段是對照、判斷時跟上一段的買訊號一起看、不是讓否定句主導決策：</p>
<table>
  <thead>
      <tr>
          <th>可觀察訊號</th>
          <th>指向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>這塊能力正是產品的差異化核心</td>
          <td>自建、控制權要握在自己手上</td>
      </tr>
      <tr>
          <td>客製需求持續撞到 SaaS 的擴展點上限</td>
          <td>外包換來的天花板開始擋路、評估自建</td>
      </tr>
      <tr>
          <td>計費隨規模線性成長、自建的 TCO 出現交叉點</td>
          <td>成本曲線翻轉、重算自建總帳</td>
      </tr>
      <tr>
          <td>資料主權或合規要求 SaaS 無法滿足</td>
          <td>控制面深度超出外包能給的範圍</td>
      </tr>
  </tbody>
</table>
<p>會把一塊能力翻回自建的訊號裡、差異化核心是最硬的一條。一塊能力如果就是產品賣點 — 推薦引擎之於內容平台、媒合演算法之於媒合服務 — 把它外包等於把競爭力外包、再貴也該自建。但「差異化核心」是最容易拿來自我說服的標籤 — 下手自建前先用買訊號表的 commodity 判準反向驗一次：同業是不是也都這樣做、做得再好客戶會不會無感？會、它其實是偽核心、「再貴也建」不適用。其餘三列是「原本買得對、條件變了該重評」：客製撞牆、成本交叉、合規不滿足、都是把選型結論拿出來重算的 tripwire、而不是一次定生死。觸發後的搬離執行 — 並行期、回切窗口、資產盤點 — 見 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出</a>。</p>
<h2 id="判讀四個真實例子">【判讀】四個真實例子</h2>
<p>例子的責任是建判斷錨點。下面四個刻意走四種不同方向 — 黏合型、買到部分搬離、永遠買、建到改買 — 避免把「逐能力外包」講成單向的故事。</p>
<p>一個親子活動 app 的形狀最能展示「決策單位是能力」。需求包含親子活動預約、室內空間遊玩預約、親職文章分享、會員資料、滿意度調查、線上諮詢。拆開來看：會員資料與空間預約接 Ragic、滿意度調查接 SurveyCake、文章只是連結跳轉、真正需要自建的差異化只有「線上諮詢內容匯整成固定格式檔」這一塊。這個 app 的本質是整合層 — 把幾個 no-code SaaS 黏起來、自己寫的部分極小。資料庫選 Supabase 還是 Neon 在這裡幾乎是次要問題、真正的工程量在「會員資料同時存在 Ragic 跟 app、要不要同步、諮詢內容怎麼流到固定格式檔」這些接縫上。逐能力看、它的決策是「五塊買、一塊建」、不是「選一個資料庫」。</p>
<p>一個成長期的 SaaS 走的是相反路徑的前半段。它早期用 Supabase 全包上線 — Postgres、Auth、Storage、Realtime 一次到位、團隊不必碰後端。業務量上來後、資料層的 query 複雜度與成本超過 Supabase 託管 Postgres 的舒適區、團隊把資料層搬到自管 PostgreSQL、但認證留在 Supabase Auth。這是逐能力遷出的典型 — 只把撞牆的那一塊（資料層）搬走、沒撞牆的（認證）留在原地、整包搬離反而是錯誤思路。</p>
<p>一個自建電商展示「永遠買」的判斷。核心交易流程、商品、訂單、庫存全部自建、因為那是差異化所在。金流則永遠接 Stripe — PCI 合規、反詐欺、各國支付方式是 Stripe 的本業、自建金流要承擔的合規與長尾成本沒有任何回報、因為「能收錢」不構成競爭優勢。這個「永遠買」是對絕大多數團隊的預設、不是無條件鐵律 — 例外要先攔在前面：做受監管清算、金融牌照或資金存管業務的團隊、接 vendor 不會把這些合規責任接走、得回到自己業態的前提判斷、別照抄「金流永遠買」。要分清這裡「買」涵蓋的是哪一層：收單（把一筆卡片交易實際跑完、完成扣款）、卡片資料、PCI 合規、各國支付方式這層、對絕大多數團隊從第一天到規模化都是買、收單就是終點。會翻回自建的是更上層的支付編排（orchestration）。當一家公司同時接多個 PSP（payment service provider，實際完成扣款的金流商、如 Stripe、Adyen）、就需要一層編排決定每筆交易走哪家、哪家失敗換哪家重試、月底跟各家對帳。這層協調的複雜度跨多業務線後超過任何單一 vendor 能給、超大規模下才會把它拿回自己手上；但拿回的是收單之上的協調邏輯、底層的 PCI 與卡片處理仍然外包。對本章設定的讀者、金流買到收單這層就是答案、編排層的自建是規模到了才會出現的 tripwire —— 而多數產品永遠到不了那個規模、orchestration 對它們是不會觸發的分支、不是必經路徑；少數例外是高度監管或特殊清算需求的團隊、小規模就可能直接 direct acquiring（跳過 PSP 直接對接收單行）。</p>
<p>站內搜尋走的是反方向 —— 建到改買。一個內容站初期用自建 Elasticsearch、隨內容成長、維運這套搜尋（叢集調校、相關性排序、同義詞、中文斷詞）吃掉的人力越來越多、而搜尋品質始終追不上專做這件事的服務。團隊把搜尋換成 Algolia — 一塊原本自建的能力、因為長尾運維成本翻轉、改成外包。方向跟前三個都不同、但判準一致：這塊能力的維護成本有沒有回報。</p>
<h2 id="判讀跨能力-bundle-的特殊判讀">【判讀】跨能力 bundle 的特殊判讀</h2>
<p>跨能力 BaaS bundle 難放進以能力分章的結構、因為它一次交付多塊能力。Supabase 同時是 Postgres、Auth、Storage、Realtime 與 Edge Functions、橫跨本系列的 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫</a>、<a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取</a>、<a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列</a>、<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署</a> 與 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安</a>。以能力分章的教材、放不下「一個 vendor 給五塊能力」這種形狀、所以 Supabase 在資料庫章節只能當 managed PostgreSQL 比較表裡的一行 — 本章是它在選型層的上層錨點。</p>
<p>bundle 的價值與鎖定同源、都來自整合。同一套認證身分貫穿資料庫的 row-level security、Storage 的存取控制與 Realtime 的訂閱權限、是 bundle 最大的賣點 — 一次設定、多塊能力一致生效、省掉自己接縫的工作。但這份整合反過來就是遷出阻力：搬走任何一塊、都要把它跟其他幾塊的整合關係拆開重接。bundle 的高遷出代價不在資料量、在這些被同一套整合綁住的能力之間。</p>
<p>判讀 bundle 的單位仍然是逐能力。Supabase 不是一個必須整包接受或整包拒絕的決定 — 你可以只用它的 Postgres 當 managed 基礎設施、認證自建或接 Auth0；也可以反過來、資料庫自管、只用 Supabase Auth。問「我需要哪幾塊」「這幾塊的整合值不值得換取遷出代價」、比問「要不要用 Supabase」更準。</p>
<p>這也澄清一個常被混為一談的並列：Supabase 跟 Neon 不在同一個外包深度。Neon 是 managed 基礎設施、純 serverless PostgreSQL、給你一個會自動擴縮的資料庫、其餘能力自理；Supabase 是 BaaS bundle、資料庫只是它的一塊。只需要一個資料庫、兩者都行、Neon 更輕、遷出代價更低；需要認證、儲存、即時同步一起到位、才是 Supabase 的賣點。把它們當同級選項比較、會錯估各自真正交付的範圍。</p>
<h2 id="權衡六方向成本與權重隨情境浮動">【權衡】六方向成本與權重隨情境浮動</h2>
<p>外包一塊能力的成本帳有六個方向、但這六個方向不是等權的 — 權重隨讀者與規模浮動、用同一張等權表套所有情境會把真正主導的軸攤平。先定權重、再看方向。</p>
<p>MVP 與個人專案主導的是三個方向：免費額度天花板、整合接縫工作量、長大後的鎖定。金流 / PCI 合規與流量穩定性對一個親子活動預約 MVP 近乎無關 — 沒有信用卡資料、沒有尖峰流量 — 但「合規」不能整類劃掉：這個 app 蒐集兒少個資、在多數司法管轄區（COPPA、GDPR-K、台灣個資法）反而是高敏感類別、同意機制與資料存放地點要照規矩走。把金流合規與流量這兩個方向跟其他四個並重討論、只會稀釋真正要看的「免費額度夠不夠、SaaS 黏起來累不累、以後搬不搬得動」。企業與規模化則相反、主導的是合規、流量穩定與計費的線性成長、免費額度天花板根本不在視野裡。</p>
<p>六個方向：</p>
<ol>
<li><strong>資安與合規</strong>：外包認證把身分攻擊面交給 vendor、對沒有資安人力的團隊是淨收益；代價是 <a href="/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">audit log</a> 細度、資料存放地點（落在哪個國家 / 區域）與稽核深度受 vendor 限制、有合規要求的業務可能直接出局。</li>
<li><strong>流量與穩定性</strong>：SaaS 的 rate limit 與 SLA 變成你的天花板、不可協商；vendor 故障時你跟著故障 — 買一塊能力等於接受一個外部單點依賴。這個依賴的極端是 vendor 自己 sunset、倒閉或被併後關停 — 跟「你想走的鎖定」相反、是 vendor 先走、後果不可逆、選有長期生存力的 vendor 是這條的隱性成本。</li>
<li><strong>伺服器與雲端成本</strong>：計費形狀（per-MAU、per-request、per-seat、免費額度上限）決定成本曲線。個人專案看免費額度夠不夠、規模化看線性成長何時跟自建的固定成本加人力出現交叉點。</li>
<li><strong>人力與操作</strong>：外包省下維運、換來 vendor 管理 — SDK 升級、API 變更追蹤、定價政策變動的因應、都是新的操作成本、只是從機房移到供應商關係。</li>
<li><strong>機會成本</strong>：買對、省下的工程時間投到差異化；買錯、付出遷出代價加 <a href="/blog/backend/knowledge-cards/vendor-lock-in/" data-link-title="Vendor Lock-In" data-link-desc="說明採用供應商產品後，其 API 與格式滲入程式碼造成的退出成本">vendor lock-in</a>。「以後可能要客製」是最常見的偽自建理由、客製需求真的出現再遷、總成本通常低於提前自建。</li>
<li><strong>整合接縫成本</strong>：每多買一塊 feature SaaS、就多一道接縫 — 資料跨 SaaS 重複（會員同時在 Ragic 跟 app）、跨來源一致性、整合維護。買越多塊、系統的真正複雜度從「每塊能力內部」移到「能力之間的縫」。這是外包換來的隱性帳、跟「省下維運」是同一個決策的反面、評估買 vs 建時要跟省下的成本一起算。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>還沒過 whole-system gate：先讀 <a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">0.21 交付形態選型</a>、判斷整個系統該不該自建。</li>
<li>成本曲線交叉點的算法：見 <a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本、風險與選型取捨</a>。</li>
<li>逐能力的 managed 選項：資料庫見 <a href="/blog/backend/01-database/vendors/postgresql/managed-pg-comparison/" data-link-title="Managed PostgreSQL Comparison" data-link-desc="RDS PostgreSQL、Aurora PostgreSQL、Cloud SQL、Azure Database for PostgreSQL、Neon、Supabase、Crunchy Bridge 的責任邊界比較">Managed PostgreSQL 比較</a>、認證見 <a href="/blog/backend/07-security-data-protection/vendors/" data-link-title="資安與資料保護 Vendor 清單" data-link-desc="規劃身份、秘密、金鑰、入口防護、供應鏈與偵測工具的服務頁撰寫順序與教學大綱">07 資安 vendor 清單</a>、佇列見 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">AWS SQS</a>。</li>
<li>外包能力撐不住要搬回自建：執行劇本見 <a href="/blog/backend/10-system-evolution/managed-platform-exit/" data-link-title="10.3 託管形態遷出：資產線盤點與並行期執行" data-link-desc="0.21 升級自建 tripwire 觸發後的執行劇本 — 把遷出拆成資料、身分、流量、整合各自的可攜性與斷點、設計舊平台與新系統的並行期與回切窗口、用部分遷出作為中繼形態">10.3 託管形態遷出</a>。</li>
<li>BaaS 的概念背景：見 <a href="/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">BaaS 知識卡片</a>。</li>
</ul>
]]></content:encoded></item></channel></rss>