<?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>Serverless on Tarragon</title><link>https://tarrragon.github.io/blog/tags/serverless/</link><description>Recent content in Serverless 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/serverless/index.xml" rel="self" type="application/rss+xml"/><item><title>Serverless function 版本、事件來源與回復流程</title><link>https://tarrragon.github.io/blog/ci/serverless-deploy/function-version-event-flow/</link><pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/serverless-deploy/function-version-event-flow/</guid><description>&lt;p>Serverless 發布流程的核心責任是把函式 artifact、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias&lt;/a>、權限與 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source&lt;/a> 一起推進。Serverless 部署看起來比長駐服務短，但每次 invocation 都依賴 runtime、IAM、event source、retry policy 與 observability；CI/CD 需要把這些條件視為發布契約。&lt;/p>
&lt;h2 id="流程定位">流程定位&lt;/h2>
&lt;p>Serverless 的風險集中在觸發條件。函式部署成功只代表新版本存在，實際風險會在 HTTP request、queue message、topic event、scheduled job 或 edge request 觸發時出現。發布流程要能區分「版本建立成功」「alias 切流量成功」「事件來源行為正確」三件事。&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>Package&lt;/td>
 &lt;td>產生 function bundle / layer&lt;/td>
 &lt;td>dependency、runtime target 是否固定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Version&lt;/td>
 &lt;td>發布 immutable function version&lt;/td>
 &lt;td>version 是否可追到 commit&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Alias / traffic&lt;/td>
 &lt;td>控制新舊版本流量&lt;/td>
 &lt;td>alias 權重、錯誤率、冷啟動&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Permission&lt;/td>
 &lt;td>限制 IAM、secret、resource policy&lt;/td>
 &lt;td>最小權限與環境隔離&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source&lt;/a>&lt;/td>
 &lt;td>管理 trigger、retry、dead-letter&lt;/td>
 &lt;td>重試與毒訊息處理是否明確&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Recovery&lt;/td>
 &lt;td>alias rollback、disable trigger、replay&lt;/td>
 &lt;td>是否能止血與修補資料&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Package 階段負責產生可執行 bundle。Serverless 常見失敗是本機 dependency 可用，但打包後缺檔、runtime target 不符、native extension 不相容或 layer 版本漂移；CI 應在接近目標 runtime 的環境做 smoke test。&lt;/p>
&lt;p>Version 階段負責建立不可變版本。直接覆蓋 &lt;code>$LATEST&lt;/code> 會讓事故追溯困難；正式流量應指向 version 或 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias&lt;/a>，讓 rollback 能把 alias 切回前一個已知版本。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias&lt;/a> / traffic 階段負責控制流量切換。HTTP function 可以用少量權重 canary；queue trigger 則要觀察 batch failure、retry、dead-letter 與 downstream side effect，因為同一個錯誤 event 可能被重試多次。&lt;/p>
&lt;p>Permission 階段負責限制 blast radius。Serverless 函式容易因部署方便而累積過大 IAM 權限；每個 function 應只拿到必要 resource、secret 與 network access，並把 production secret 與 preview / staging 隔離。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source&lt;/a> 階段負責定義失敗重送語意。Queue、topic、object storage、HTTP 與 scheduler 的錯誤行為不同；CI/CD 文件要記錄 retry 次數、dead-letter destination、batch size、concurrency limit 與 replay 條件。&lt;/p>
&lt;p>Recovery 階段負責止血。Serverless 常見止血方式是 alias rollback、停用 trigger、降低 concurrency、清理毒訊息、重放事件或 forward fix；只回退 code 版本不一定能處理已經排入 queue 的事件。&lt;/p></description><content:encoded><![CDATA[<p>Serverless 發布流程的核心責任是把函式 artifact、<a href="/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias</a>、權限與 <a href="/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source</a> 一起推進。Serverless 部署看起來比長駐服務短，但每次 invocation 都依賴 runtime、IAM、event source、retry policy 與 observability；CI/CD 需要把這些條件視為發布契約。</p>
<h2 id="流程定位">流程定位</h2>
<p>Serverless 的風險集中在觸發條件。函式部署成功只代表新版本存在，實際風險會在 HTTP request、queue message、topic event、scheduled job 或 edge request 觸發時出現。發布流程要能區分「版本建立成功」「alias 切流量成功」「事件來源行為正確」三件事。</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>責任</th>
          <th>判讀訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Package</td>
          <td>產生 function bundle / layer</td>
          <td>dependency、runtime target 是否固定</td>
      </tr>
      <tr>
          <td>Version</td>
          <td>發布 immutable function version</td>
          <td>version 是否可追到 commit</td>
      </tr>
      <tr>
          <td>Alias / traffic</td>
          <td>控制新舊版本流量</td>
          <td>alias 權重、錯誤率、冷啟動</td>
      </tr>
      <tr>
          <td>Permission</td>
          <td>限制 IAM、secret、resource policy</td>
          <td>最小權限與環境隔離</td>
      </tr>
      <tr>
          <td><a href="/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source</a></td>
          <td>管理 trigger、retry、dead-letter</td>
          <td>重試與毒訊息處理是否明確</td>
      </tr>
      <tr>
          <td>Recovery</td>
          <td>alias rollback、disable trigger、replay</td>
          <td>是否能止血與修補資料</td>
      </tr>
  </tbody>
</table>
<p>Package 階段負責產生可執行 bundle。Serverless 常見失敗是本機 dependency 可用，但打包後缺檔、runtime target 不符、native extension 不相容或 layer 版本漂移；CI 應在接近目標 runtime 的環境做 smoke test。</p>
<p>Version 階段負責建立不可變版本。直接覆蓋 <code>$LATEST</code> 會讓事故追溯困難；正式流量應指向 version 或 <a href="/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias</a>，讓 rollback 能把 alias 切回前一個已知版本。</p>
<p><a href="/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias</a> / traffic 階段負責控制流量切換。HTTP function 可以用少量權重 canary；queue trigger 則要觀察 batch failure、retry、dead-letter 與 downstream side effect，因為同一個錯誤 event 可能被重試多次。</p>
<p>Permission 階段負責限制 blast radius。Serverless 函式容易因部署方便而累積過大 IAM 權限；每個 function 應只拿到必要 resource、secret 與 network access，並把 production secret 與 preview / staging 隔離。</p>
<p><a href="/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source</a> 階段負責定義失敗重送語意。Queue、topic、object storage、HTTP 與 scheduler 的錯誤行為不同；CI/CD 文件要記錄 retry 次數、dead-letter destination、batch size、concurrency limit 與 replay 條件。</p>
<p>Recovery 階段負責止血。Serverless 常見止血方式是 alias rollback、停用 trigger、降低 concurrency、清理毒訊息、重放事件或 forward fix；只回退 code 版本不一定能處理已經排入 queue 的事件。</p>
<h2 id="事件來源判讀">事件來源判讀</h2>
<p>事件來源判讀的責任是找出失敗是否可重試。Serverless 常被誤判為「函式自己失敗」，但實際根因可能是 event schema、權限、上游重試或下游限流。</p>
<table>
  <thead>
      <tr>
          <th>Event source</th>
          <th>常見失敗</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HTTP / API</td>
          <td>status code、timeout、冷啟動</td>
          <td>看 latency、concurrency、alias</td>
      </tr>
      <tr>
          <td>Queue</td>
          <td>batch failure、毒訊息、重試風暴</td>
          <td>看 DLQ、batch size、visibility timeout</td>
      </tr>
      <tr>
          <td>Topic</td>
          <td>event schema 漂移</td>
          <td>驗證 publisher / subscriber 契約</td>
      </tr>
      <tr>
          <td>Object store</td>
          <td>權限或路徑 pattern 錯誤</td>
          <td>檢查 resource policy 與 filter</td>
      </tr>
      <tr>
          <td>Scheduler</td>
          <td>timezone、重入、上次執行未完成</td>
          <td>檢查 idempotency 與 lock</td>
      </tr>
  </tbody>
</table>
<p>這張表讓 release failure 能被導向正確 owner。若 event schema 變了，修 function 可能只是表面補丁；真正的 gate 要加在 publisher contract 或 sample event validation。</p>
<h2 id="最小發布-gate">最小發布 gate</h2>
<p>Serverless workflow 的最小 gate 應覆蓋 package、permission、event 與 alias。缺其中一段，部署成功就可能只是建立了一個尚未被驗證的函式版本。</p>
<ol>
<li>Package bundle，固定 runtime target 與 dependency。</li>
<li>對 bundle 執行 unit / contract / sample event test。</li>
<li>用 least privilege policy 做 deploy dry run 或 policy diff。</li>
<li>發布 immutable function version。</li>
<li>用 alias 將少量流量導向新版本。</li>
<li>觀察 error、latency、retry、DLQ 與 downstream 指標。</li>
<li>指標穩定後提高 alias 權重或完成切換。</li>
<li>指標觸發 tripwire 時切回 alias、停用 trigger 或啟動 repair。</li>
</ol>
<p>這個流程把 Serverless 發布從「上傳函式」提升成可回復流程。對事件驅動函式而言，trigger 與 retry policy 是發布契約的一部分。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Serverless 部署總覽：回 <a href="../">Serverless 部署 CI/CD</a>。</li>
<li>Rollout 概念：讀 <a href="/blog/ci/knowledge-cards/rollout-strategy/" data-link-title="Rollout Strategy" data-link-desc="說明新版本如何以可控節奏推進到全部流量">Rollout Strategy</a>。</li>
<li>失敗處理：讀 <a href="../../github-actions-failure-flow/">CI 失敗到修復發布流程</a>。</li>
</ul>
]]></content:encoded></item><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>Serverless 部署 CI/CD</title><link>https://tarrragon.github.io/blog/ci/serverless-deploy/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/serverless-deploy/</guid><description>&lt;p>Serverless 部署 CI/CD 的核心責任是把函式型服務安全推進到受管執行環境。它和長駐服務不同，風險集中在 artifact 打包、runtime 相容、權限設定、版本別名與冷啟動行為。&lt;/p>
&lt;h2 id="場域定位">場域定位&lt;/h2>
&lt;p>Serverless 發布通常以函式版本為單位，並透過 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias&lt;/a> 或流量權重切換。部署步驟看起來短，但對權限、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source&lt;/a>、重試政策與 observability 欄位要求很高。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>Serverless 部署常見責任&lt;/th>
 &lt;th>判讀訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Build&lt;/td>
 &lt;td>function bundle、dependency、runtime target&lt;/td>
 &lt;td>package 是否可重現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Deploy&lt;/td>
 &lt;td>function version、alias、traffic shift&lt;/td>
 &lt;td>新舊版本是否可並存&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Permission&lt;/td>
 &lt;td>IAM、resource policy、secret scope&lt;/td>
 &lt;td>執行是否具最小權限&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Event Source&lt;/td>
 &lt;td>queue/topic/http trigger 設定&lt;/td>
 &lt;td>重試與死信策略是否明確&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Recovery&lt;/td>
 &lt;td>alias rollback、disable trigger&lt;/td>
 &lt;td>故障時是否可快速止血&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見注意事項">常見注意事項&lt;/h2>
&lt;ul>
&lt;li>部署前要先驗證 runtime 與依賴版本，避免 deploy 成功但 invocation 失敗。&lt;/li>
&lt;li>事件觸發型函式要明確設定 retry、dead-letter 或回放策略。&lt;/li>
&lt;li>權限設定要收斂到最小範圍，避免函式擴權風險。&lt;/li>
&lt;li>冷啟動與併發上限要納入發布後觀測指標。&lt;/li>
&lt;/ul>
&lt;h2 id="學習路線">學習路線&lt;/h2>
&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>&lt;a href="function-version-event-flow/">Serverless function 版本、事件來源與回復流程&lt;/a>&lt;/td>
 &lt;td>Function version and event&lt;/td>
 &lt;td>管理版本別名、事件來源、權限與回復&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Serverless 發布主流程：讀 &lt;a href="function-version-event-flow/">Serverless function 版本、事件來源與回復流程&lt;/a>。&lt;/li>
&lt;li>Gate 原理：讀 &lt;a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界&lt;/a>。&lt;/li>
&lt;li>失敗處理：讀 &lt;a href="../github-actions-failure-flow/">CI 失敗到修復發布流程&lt;/a>。&lt;/li>
&lt;li>Backend 相關概念：讀 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/local-worker/" data-link-title="Local Worker" data-link-desc="說明同一個 process 內的背景工作模型與其生命週期邊界">Serverless / worker 相關知識卡&lt;/a>。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Serverless 部署 CI/CD 的核心責任是把函式型服務安全推進到受管執行環境。它和長駐服務不同，風險集中在 artifact 打包、runtime 相容、權限設定、版本別名與冷啟動行為。</p>
<h2 id="場域定位">場域定位</h2>
<p>Serverless 發布通常以函式版本為單位，並透過 <a href="/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias</a> 或流量權重切換。部署步驟看起來短，但對權限、<a href="/blog/ci/knowledge-cards/event-source/" data-link-title="Event Source" data-link-desc="說明 serverless 與事件驅動流程中觸發來源如何影響 retry、dead-letter 與回復策略">Event Source</a>、重試政策與 observability 欄位要求很高。</p>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Serverless 部署常見責任</th>
          <th>判讀訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>function bundle、dependency、runtime target</td>
          <td>package 是否可重現</td>
      </tr>
      <tr>
          <td>Deploy</td>
          <td>function version、alias、traffic shift</td>
          <td>新舊版本是否可並存</td>
      </tr>
      <tr>
          <td>Permission</td>
          <td>IAM、resource policy、secret scope</td>
          <td>執行是否具最小權限</td>
      </tr>
      <tr>
          <td>Event Source</td>
          <td>queue/topic/http trigger 設定</td>
          <td>重試與死信策略是否明確</td>
      </tr>
      <tr>
          <td>Recovery</td>
          <td>alias rollback、disable trigger</td>
          <td>故障時是否可快速止血</td>
      </tr>
  </tbody>
</table>
<h2 id="常見注意事項">常見注意事項</h2>
<ul>
<li>部署前要先驗證 runtime 與依賴版本，避免 deploy 成功但 invocation 失敗。</li>
<li>事件觸發型函式要明確設定 retry、dead-letter 或回放策略。</li>
<li>權限設定要收斂到最小範圍，避免函式擴權風險。</li>
<li>冷啟動與併發上限要納入發布後觀測指標。</li>
</ul>
<h2 id="學習路線">學習路線</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="function-version-event-flow/">Serverless function 版本、事件來源與回復流程</a></td>
          <td>Function version and event</td>
          <td>管理版本別名、事件來源、權限與回復</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Serverless 發布主流程：讀 <a href="function-version-event-flow/">Serverless function 版本、事件來源與回復流程</a>。</li>
<li>Gate 原理：讀 <a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</li>
<li>失敗處理：讀 <a href="../github-actions-failure-flow/">CI 失敗到修復發布流程</a>。</li>
<li>Backend 相關概念：讀 <a href="/blog/backend/knowledge-cards/local-worker/" data-link-title="Local Worker" data-link-desc="說明同一個 process 內的背景工作模型與其生命週期邊界">Serverless / worker 相關知識卡</a>。</li>
</ul>
]]></content:encoded></item><item><title>4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨</title><link>https://tarrragon.github.io/blog/llm/04-applications/static-and-serverless-rag-deployment/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/static-and-serverless-rag-deployment/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &amp;#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model&lt;/a> 寫的是「RAG 在做什麼、embedding 怎麼選」、預設「有 backend server」可跑 embedding 跟 LLM。但實際大量場景是&lt;strong>沒 backend&lt;/strong> — 個人 blog（Hugo / Jekyll / Astro）想加智能搜尋、docs site 想做 LLM 對話、demo 想離線跑。本章把這條「靜態 / serverless RAG」路線拆成四個方案、配合靜態場景&lt;strong>特有的資安議題&lt;/strong>（這些議題模組六沒覆蓋、屬本章新增）。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>區分四種 RAG deployment 方案（純前端 / edge serverless / RAG SaaS / 純文字 search）。&lt;/li>
&lt;li>對自己場景判斷該選哪個方案、看資料量 / 隱私 / 預算。&lt;/li>
&lt;li>認識靜態場景特有的資安議題：API key 暴露、CORS、abuse、第三方 SaaS 供應鏈、client-side 模型完整性。&lt;/li>
&lt;li>知道哪些資安議題在 &lt;a href="https://tarrragon.github.io/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六&lt;/a> 已覆蓋、哪些是本章獨有。&lt;/li>
&lt;/ol>
&lt;h2 id="為什麼這個議題重要">為什麼這個議題重要&lt;/h2>
&lt;p>傳統 RAG 教材預設架構：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">User → backend server → embedding API → vector DB → LLM API → response&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>需要 backend 可執行 server-side code、藏 API key、控制 rate limit。但個人開發者場景常見的 deployment：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>Backend？&lt;/th>
 &lt;th>部署方式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>個人 Hugo blog&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>GitHub Pages / Cloudflare Pages&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開源專案 docs site&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>GitHub Pages / Netlify / Vercel&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>商品 landing page&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>CDN + S3&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Static-export Next.js / Astro&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>同上&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這些場景跟「個人 dev 跑本地 LLM」並列、是教材的合理覆蓋面。&lt;/p>
&lt;h2 id="四種-deployment-方案總覽">四種 deployment 方案總覽&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl"> embedding vector LLM call
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 搜尋 DB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">方案 1 純前端 browser browser browser（WebLLM）或 user-key 直 call
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">方案 2 edge serverless edge fn edge DB edge fn → LLM API
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">方案 3 RAG SaaS SaaS SaaS SaaS（或自 call）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">方案 4 純文字 search N/A static idx N/A（不是 RAG）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>四方案快速對比：&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> 跟 <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model</a> 寫的是「RAG 在做什麼、embedding 怎麼選」、預設「有 backend server」可跑 embedding 跟 LLM。但實際大量場景是<strong>沒 backend</strong> — 個人 blog（Hugo / Jekyll / Astro）想加智能搜尋、docs site 想做 LLM 對話、demo 想離線跑。本章把這條「靜態 / serverless RAG」路線拆成四個方案、配合靜態場景<strong>特有的資安議題</strong>（這些議題模組六沒覆蓋、屬本章新增）。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>區分四種 RAG deployment 方案（純前端 / edge serverless / RAG SaaS / 純文字 search）。</li>
<li>對自己場景判斷該選哪個方案、看資料量 / 隱私 / 預算。</li>
<li>認識靜態場景特有的資安議題：API key 暴露、CORS、abuse、第三方 SaaS 供應鏈、client-side 模型完整性。</li>
<li>知道哪些資安議題在 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 已覆蓋、哪些是本章獨有。</li>
</ol>
<h2 id="為什麼這個議題重要">為什麼這個議題重要</h2>
<p>傳統 RAG 教材預設架構：</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">User → backend server → embedding API → vector DB → LLM API → response</span></span></code></pre></div><p>需要 backend 可執行 server-side code、藏 API key、控制 rate limit。但個人開發者場景常見的 deployment：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>Backend？</th>
          <th>部署方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>個人 Hugo blog</td>
          <td>無</td>
          <td>GitHub Pages / Cloudflare Pages</td>
      </tr>
      <tr>
          <td>開源專案 docs site</td>
          <td>無</td>
          <td>GitHub Pages / Netlify / Vercel</td>
      </tr>
      <tr>
          <td>商品 landing page</td>
          <td>無</td>
          <td>CDN + S3</td>
      </tr>
      <tr>
          <td>Static-export Next.js / Astro</td>
          <td>無</td>
          <td>同上</td>
      </tr>
  </tbody>
</table>
<p>這些場景跟「個人 dev 跑本地 LLM」並列、是教材的合理覆蓋面。</p>
<h2 id="四種-deployment-方案總覽">四種 deployment 方案總覽</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                          embedding   vector       LLM call
</span></span><span class="line"><span class="ln">2</span><span class="cl">                          搜尋          DB
</span></span><span class="line"><span class="ln">3</span><span class="cl">方案 1 純前端            browser       browser     browser（WebLLM）或 user-key 直 call
</span></span><span class="line"><span class="ln">4</span><span class="cl">方案 2 edge serverless   edge fn       edge DB     edge fn → LLM API
</span></span><span class="line"><span class="ln">5</span><span class="cl">方案 3 RAG SaaS          SaaS          SaaS        SaaS（或自 call）
</span></span><span class="line"><span class="ln">6</span><span class="cl">方案 4 純文字 search     N/A           static idx  N/A（不是 RAG）</span></span></code></pre></div><p>四方案快速對比：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>1 純前端</th>
          <th>2 edge serverless</th>
          <th>3 SaaS</th>
          <th>4 純文字 search</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>是否「真 RAG」</td>
          <td>是</td>
          <td>是</td>
          <td>是</td>
          <td><strong>否</strong>（無 LLM）</td>
      </tr>
      <tr>
          <td>隱私</td>
          <td>最強（不離 browser）</td>
          <td>中（信 edge provider）</td>
          <td>弱（信 SaaS）</td>
          <td>最強</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>完全 zero（build 一次）</td>
          <td>每 query 付 edge + LLM</td>
          <td>免費 tier / 按量計費</td>
          <td>Zero</td>
      </tr>
      <tr>
          <td>規模上限</td>
          <td>&lt; 10K chunks</td>
          <td>1M+</td>
          <td>視服務</td>
          <td>視工具</td>
      </tr>
      <tr>
          <td>開發複雜度</td>
          <td>中（要 build pipeline）</td>
          <td>中高（要寫 edge fn）</td>
          <td>低（API 直接用）</td>
          <td>低</td>
      </tr>
      <tr>
          <td>主要資安議題</td>
          <td>模型完整性、user-key 暴露</td>
          <td>edge provider 信任</td>
          <td>SaaS 信任 + 供應鏈</td>
          <td>較少（無 LLM）</td>
      </tr>
  </tbody>
</table>
<h2 id="方案-1純前端-ragbrowser-side-everything">方案 1：純前端 RAG（browser-side everything）</h2>
<p>整個 RAG pipeline 都跑在使用者瀏覽器：</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">Build time（Hugo build / CI pipeline）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  content/*.md
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    ↓ 抽段、chunk
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ↓ embedding model（Node.js 版 sentence-transformers）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  embeddings.json（每個 chunk 一個 vector）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ↓ 跟 HTML 一起 deploy
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Runtime（user browser）：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  User query
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ↓ load @xenova/transformers + embeddings.json（首訪載 ~50MB）
</span></span><span class="line"><span class="ln">11</span><span class="cl">    ↓ embed query in browser
</span></span><span class="line"><span class="ln">12</span><span class="cl">    ↓ cosine similarity vs embeddings.json
</span></span><span class="line"><span class="ln">13</span><span class="cl">  top-K chunks
</span></span><span class="line"><span class="ln">14</span><span class="cl">    ↓ LLM call（兩條子路線、見下）
</span></span><span class="line"><span class="ln">15</span><span class="cl">  Response in browser</span></span></code></pre></div><p>LLM 的兩條子路線：</p>
<table>
  <thead>
      <tr>
          <th>子路線</th>
          <th>機制</th>
          <th>取捨</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong><a href="/blog/llm/knowledge-cards/client-side-llm/" data-link-title="Client-Side LLM / Embedding" data-link-desc="在 browser 內直接跑 LLM 或 embedding model 的 paradigm、靜態網站做 RAG 的關鍵基底">Client-side LLM</a></strong></td>
          <td>WebLLM / wllama 跑 &lt; 4B model</td>
          <td>完全離線、首訪載 1-3GB 模型、隱私最強</td>
      </tr>
      <tr>
          <td><strong>User 自帶 API key</strong></td>
          <td>前端讀 localStorage 的 key、直 call API</td>
          <td>高品質（雲端旗艦）、key 暴露、需要使用者授信</td>
      </tr>
  </tbody>
</table>
<p>實作概要：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Build time（Node.js script）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">npx @xenova/transformers-cli embed content/*.md &gt; static/embeddings.json
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Frontend（簡化版）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">import <span class="o">{</span> pipeline <span class="o">}</span> from <span class="s1">&#39;@xenova/transformers&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">const <span class="nv">embedder</span> <span class="o">=</span> await pipeline<span class="o">(</span><span class="s1">&#39;feature-extraction&#39;</span>, <span class="s1">&#39;nomic-embed-text-v1.5&#39;</span><span class="o">)</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">const <span class="nv">queryVec</span> <span class="o">=</span> await embedder<span class="o">(</span>userQuery, <span class="o">{</span> pooling: <span class="s1">&#39;mean&#39;</span> <span class="o">})</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">const <span class="nv">ranked</span> <span class="o">=</span> embeddings.map<span class="o">(</span><span class="nv">c</span> <span class="o">=</span>&gt; <span class="o">({</span> ...c, score: cosineSim<span class="o">(</span>c.vec, queryVec.data<span class="o">)</span> <span class="o">}))</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">                          .sort<span class="o">((</span>a,b<span class="o">)</span> <span class="o">=</span>&gt; b.score - a.score<span class="o">)</span>.slice<span class="o">(</span>0, 5<span class="o">)</span><span class="p">;</span></span></span></code></pre></div><p>規模上限：</p>
<ul>
<li>&lt; 1000 chunks：embeddings.json ~ 4MB（1024-dim float32）、輕鬆</li>
<li>1K-10K：~40MB、首訪載入慢但可接受</li>
<li>10K+：純前端開始勉強、考慮方案 2</li>
</ul>
<p><strong>適合場景</strong>：個人 blog、docs site、demo、隱私敏感、規模 &lt; 10K chunks。</p>
<h2 id="方案-2靜態--edge-serverless">方案 2：靜態 + edge serverless</h2>
<p>「靜態主站 + edge function 處理動態請求」：</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">靜態前端（HTML / JS、Hugo / Astro）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓ fetch /api/rag
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Edge function（Cloudflare Workers / Vercel Edge / Netlify Functions）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Embedding API（OpenAI / Voyage）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Vector DB（Cloudflare Vectorize / Pinecone / Turso vector / Upstash Vector）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">LLM API（OpenAI / Anthropic / Cloudflare AI Gateway）
</span></span><span class="line"><span class="ln">10</span><span class="cl">   ↓ response
</span></span><span class="line"><span class="ln">11</span><span class="cl">靜態前端</span></span></code></pre></div><p>對使用者體感跟「有 backend」一樣、但你不用維護 server / 不用 sysadmin。</p>
<p>主流元件搭配：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>Cloudflare 全家桶</th>
          <th>Vercel / 其他</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Edge runtime</td>
          <td>Workers</td>
          <td>Vercel Edge / Netlify Functions</td>
      </tr>
      <tr>
          <td>Vector DB</td>
          <td>Cloudflare Vectorize</td>
          <td>Pinecone / Turso / Upstash</td>
      </tr>
      <tr>
          <td>Embedding</td>
          <td>Workers AI 內建模型 / OpenAI</td>
          <td>OpenAI / Voyage</td>
      </tr>
      <tr>
          <td>LLM</td>
          <td>Workers AI / AI Gateway 轉發</td>
          <td>OpenAI / Anthropic</td>
      </tr>
  </tbody>
</table>
<p>關鍵特性：</p>
<ol>
<li><strong>API key 不暴露在 browser</strong>：edge function 內讀環境變數、安全</li>
<li><strong>可加 rate limit</strong>：edge function 內判斷 client IP / user agent、避免 abuse</li>
<li><strong>Build-time index 仍重要</strong>：embedding ingestion 通常在 build 階段、不在 runtime</li>
<li><strong>Edge cold start</strong>：第一次 query latency 略高（~100ms 額外）、後續 hot 路徑快</li>
</ol>
<p><strong>適合場景</strong>：規模 1K-100K chunks、想保留近 backend 體驗、可接受少量 cost。這條路線一旦升級到有 backend 的 vector DB、storage 選型（index 結構、維度、成本）就回到 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a> 的判讀。</p>
<h2 id="方案-3靜態--rag-saas">方案 3：靜態 + RAG SaaS</h2>
<p>把整個 RAG stack 外包：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>角色</th>
          <th>免費 tier 上限</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Algolia</td>
          <td>搜尋 + 向量檢索一條龍、build time 同步</td>
          <td>10K records、10K search / month</td>
      </tr>
      <tr>
          <td>Pinecone Cloud</td>
          <td>純 vector DB、自己 call embedding + LLM</td>
          <td>100K vectors（starter）</td>
      </tr>
      <tr>
          <td>Weaviate Cloud</td>
          <td>同上、hybrid search 內建</td>
          <td>14 天 trial</td>
      </tr>
      <tr>
          <td>MeiliSearch Cloud</td>
          <td>BM25 + vector hybrid</td>
          <td>試用</td>
      </tr>
  </tbody>
</table>
<p>API key 設計：</p>
<ul>
<li><strong>search-only key</strong>：只能查詢、無寫入權限、<strong>可安全暴露在 browser</strong>（這是設計支援的）</li>
<li><strong>admin key</strong>：build time CI 用、有寫入權限、必須藏 server-side</li>
</ul>
<p>前端範例（Algolia）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="nx">algoliasearch</span><span class="p">(</span><span class="s1">&#39;APP_ID&#39;</span><span class="p">,</span> <span class="s1">&#39;SEARCH_ONLY_KEY&#39;</span><span class="p">);</span>  <span class="c1">// 可公開
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="nx">client</span><span class="p">.</span><span class="nx">initIndex</span><span class="p">(</span><span class="s1">&#39;my-blog&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">hits</span> <span class="p">}</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">index</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">userQuery</span><span class="p">,</span> <span class="p">{</span> <span class="nx">hitsPerPage</span><span class="o">:</span> <span class="mi">5</span> <span class="p">});</span></span></span></code></pre></div><p><strong>適合場景</strong>：想最快上線、不在乎 vendor lock-in、規模中小、retrieval-only（不需要 LLM 對話）。</p>
<h2 id="方案-4靜態--純文字-search不是真-rag">方案 4：靜態 + 純文字 search（不是真 RAG）</h2>
<p>Pagefind、Stork、lunr.js、FlexSearch — build time 產靜態 search index、純前端查詢。</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>機制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pagefind</td>
          <td>static-first、自動 chunking、CJK 友善</td>
      </tr>
      <tr>
          <td>Stork</td>
          <td>Rust 寫的 keyword search、輕量</td>
      </tr>
      <tr>
          <td>lunr.js</td>
          <td>純 JS、tf-idf BM25 風格</td>
      </tr>
      <tr>
          <td>FlexSearch</td>
          <td>同上、體積更小</td>
      </tr>
  </tbody>
</table>
<p><strong>這不是 RAG</strong>：</p>
<ol>
<li><strong>無 embedding similarity</strong>：keyword / fuzzy match、不是語意相似</li>
<li><strong>無 LLM augmentation</strong>：只列文章連結、不生成回答</li>
<li><strong>算 retrieval 的「字面」變體</strong>：見 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> 的「語意 vs 字面」段</li>
</ol>
<p><strong>適合場景</strong>：blog 內搜尋只需要找文章、不需要對話、極致 zero-cost。</p>
<h2 id="規模門檻什麼時候該升級方案">規模門檻：什麼時候該升級方案</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">&lt; 1K chunks                    → 方案 1 純前端、最簡單
</span></span><span class="line"><span class="ln">2</span><span class="cl">1K - 10K chunks                → 方案 1 或 方案 4
</span></span><span class="line"><span class="ln">3</span><span class="cl">10K - 100K chunks              → 方案 2 edge serverless
</span></span><span class="line"><span class="ln">4</span><span class="cl">100K+ chunks                   → 完整 backend RAG（不再是「靜態」場景）
</span></span><span class="line"><span class="ln">5</span><span class="cl">非 RAG、只要找文章             → 方案 4（Pagefind 等）</span></span></code></pre></div><h2 id="靜態場景特有的資安議題">靜態場景特有的資安議題</h2>
<p>本章節最重要的部分。靜態 / serverless RAG 有些議題模組六沒覆蓋、要在本章補。</p>
<h3 id="1-api-key-暴露--靜態場景的根本問題">1. API key 暴露 — 靜態場景的根本問題</h3>
<p><strong>核心衝突</strong>：靜態網站沒 server-side runtime、藏不了 secret。任何寫在前端 JS / 編進 HTML 的東西、使用者按 F12 都看得到。</p>
<p>對應到 RAG：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>能否前端持有 key</th>
          <th>緩解</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Embedding API（生成方）</td>
          <td>否（admin key 不該暴露）</td>
          <td>build time 用、不放前端</td>
      </tr>
      <tr>
          <td>LLM API（生成方）</td>
          <td>否</td>
          <td>改方案 2 用 edge、或讓使用者自帶 key</td>
      </tr>
      <tr>
          <td>Vector DB（read）</td>
          <td><strong>可</strong>（search-only key 設計支援）</td>
          <td>API 設計時就分權、search-only 可公開</td>
      </tr>
      <tr>
          <td>完整 LLM 跑在前端</td>
          <td>N/A（無 server-side key）</td>
          <td>方案 1 的 Client-side LLM 子路線</td>
      </tr>
  </tbody>
</table>
<p>如果要 LLM 對話功能、三條合法路線：</p>
<ol>
<li><strong>使用者自帶 API key</strong>（如 Anthropic / OpenAI）、存 localStorage、前端直接 call API — 適合 power user、需要使用者授信</li>
<li><strong>WebLLM / wllama 跑前端 LLM</strong> — 模型在 browser、不需 server-side key</li>
<li><strong>方案 2 edge serverless</strong> — key 藏在 edge function、就不是純靜態了</li>
</ol>
<p>寫死 API key 在前端 JS 等於把 key 公開、會被 scraper 撿走燒爆 quota — 這是 <strong>anti-pattern</strong>、跟 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 跨雲端 / 本地資料邊界</a> 提到「API key 寫死 config」的延伸版（前端更嚴重、所有訪客都看得到）。</p>
<h3 id="2-user-query-隱私">2. User query 隱私</h3>
<p>靜態場景的 query 走向：</p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>Query 走向</th>
          <th>誰能看到</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1 純前端 + WebLLM</td>
          <td>從不離 browser</td>
          <td>只有使用者本人</td>
      </tr>
      <tr>
          <td>1 + user API key</td>
          <td>Browser → 雲端 vendor</td>
          <td>該 vendor（依政策）</td>
      </tr>
      <tr>
          <td>2 edge serverless</td>
          <td>Browser → edge → 雲端 API</td>
          <td>Edge provider + LLM vendor</td>
      </tr>
      <tr>
          <td>3 SaaS</td>
          <td>Browser → SaaS</td>
          <td>SaaS provider</td>
      </tr>
  </tbody>
</table>
<p>對應 framing 跟 <a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 隱私資料流</a> 同源 — 但靜態場景的特殊性是「<strong>前端直接出去</strong>」、不像 backend 場景可以加一層中介控制。</p>
<p>特別注意：</p>
<ol>
<li><strong>方案 3 SaaS 的 query 隱私</strong>：Algolia / Pinecone 都會 log query、依政策可能用於改進服務；對隱私敏感場景不適合</li>
<li><strong>Edge provider 的 region</strong>：Cloudflare Workers 的 edge node 可能在跟使用者不同 region 處理、跨境資料法規（GDPR 等）要考慮</li>
<li><strong>Browser extension 偷 query</strong>：使用者裝的 plugin 可能 access 整個頁面、包含 RAG 介面內的 query</li>
</ol>
<h3 id="3-cors--同源策略--browser-特有的安全模型">3. CORS / 同源策略 — Browser 特有的安全模型</h3>
<p>靜態前端 call 任意 API 會撞 CORS（Cross-Origin Resource Sharing）：</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">靜態網站：https://my-blog.com
</span></span><span class="line"><span class="ln">2</span><span class="cl">要 call：https://api.openai.com/v1/...
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">4</span><span class="cl">Browser 檢查 OpenAI 是否在 Access-Control-Allow-Origin 含 my-blog.com
</span></span><span class="line"><span class="ln">5</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">6</span><span class="cl">OpenAI 預設允許所有 origin（為了讓前端 SDK 能用）→ 通過
</span></span><span class="line"><span class="ln">7</span><span class="cl">某些 API（Anthropic 早期版本）不允許 browser 直 call → 失敗、必須走 edge</span></span></code></pre></div><p>判讀：</p>
<ul>
<li><strong>能在 browser 直 call 的 API</strong>：OpenAI、Voyage、Algolia（search-only）等明確設計 browser-friendly 的服務</li>
<li><strong>不能 browser 直 call、要 edge proxy</strong>：許多企業 LLM API、私有 vector DB、需要 server-only credentials 的服務</li>
</ul>
<p>CORS 不是「資安漏洞」、是 browser 對「JS 從一個網站 call 另一個網站」的設計約束、用來保護使用者。要繞 CORS 要嗎服務商配合（設 ACAO）、要嗎用 edge function proxy。</p>
<h3 id="4-第三方-saas-信任--跟-60-同源對象換">4. 第三方 SaaS 信任 — 跟 6.0 同源、對象換</h3>
<p><a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0 模型供應鏈與信任邊界</a> 處理的是「<strong>模型權重的信任</strong>」。靜態 RAG SaaS（Algolia / Pinecone / Weaviate Cloud）引入另一條供應鏈：</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">模型供應鏈（6.0 覆蓋）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  原作者 → quantizer → registry → 你機器
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">RAG SaaS 供應鏈（本章新增）：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  你的 content → SaaS embedding service → SaaS vector DB → SaaS retrieval
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    └──────── 全程在 SaaS 內、你信任 SaaS 沒做以下事 ────────┘
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">              - 把你 index 用於訓練他們自己的模型
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">              - 把你 query log 賣給第三方
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">              - 沒做適當 isolation（你跟其他客戶的資料）
</span></span><span class="line"><span class="ln">10</span><span class="cl">              - 沒處理好 supply chain（他們用的 base embedding model）</span></span></code></pre></div><p>判讀類似 <a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 物理 vs 合約保證</a>：本地方案是物理保證（資料不離 browser）、SaaS 方案是合約保證（信 SaaS 的 ToS）。</p>
<h3 id="5-rate-limit--abuse--前端被-scrape-後濫用">5. Rate limit / abuse — 前端被 scrape 後濫用</h3>
<p>靜態 RAG 的特殊 abuse 路徑：</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">攻擊者掃到你的 demo blog
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ 找到前端載入的 embedding endpoint / LLM endpoint
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓ 直接從攻擊者 server 重複 call（不經 browser）
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓ 你的 LLM API quota 燒爆 / SaaS 配額耗光</span></span></code></pre></div><p>緩解：</p>
<ol>
<li><strong>方案 2 edge</strong> + 加 rate limit by IP / token bucket：edge function 內 reject 過量請求</li>
<li><strong>方案 1 純前端 + WebLLM</strong>：根本沒 server-side endpoint 可被 abuse、最安全</li>
<li><strong>方案 3 SaaS</strong> + 用 search-only key 並設 query 上限：SaaS 通常內建 quota</li>
<li><strong>CAPTCHA / Turnstile</strong>：邊緣防護</li>
</ol>
<p>絕對不該做：把 OpenAI / Anthropic API key 寫在前端 JS、想用 rate limit 阻擋 — 攻擊者拿到 key 後不會經過你的 rate limit。</p>
<h3 id="6-client-side-llm-的模型完整性">6. Client-side LLM 的模型完整性</h3>
<p><a href="/blog/llm/knowledge-cards/client-side-llm/" data-link-title="Client-Side LLM / Embedding" data-link-desc="在 browser 內直接跑 LLM 或 embedding model 的 paradigm、靜態網站做 RAG 的關鍵基底">Client-side LLM</a> 把幾 GB 模型權重下載到 browser、引入新的供應鏈面：</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">你的網站
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ &lt;script&gt; 載入 WebLLM runtime（CDN）
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓ runtime 從 HuggingFace CDN 抓 model weights
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓ 使用者 browser 跑模型</span></span></code></pre></div><p>風險：</p>
<ol>
<li><strong>CDN 被 compromise</strong>：WebLLM runtime 或 model weights 在 CDN 上被換、注入 backdoor</li>
<li><strong>HTTPS 之外無額外驗證</strong>：不像本地 <a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">GGUF + hash 比對</a>、browser 載模型純信 CDN + HTTPS</li>
<li><strong>使用者本機沒 inventory 記錄</strong>：跟 <a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0</a> 推薦的「下載後記 hash」對比、browser 沒這機制</li>
</ol>
<p>緩解：</p>
<ol>
<li><strong>Subresource Integrity（SRI）</strong>：HTML 的 <code>&lt;script integrity=&quot;sha384-...&quot;&gt;</code> 屬性、browser 自動驗證 hash</li>
<li><strong>CSP（Content Security Policy）</strong>：限制可載入的 script / image source、減少 supply chain attack 面</li>
<li><strong>挑大廠 CDN</strong>：Cloudflare / jsdelivr / unpkg 等被 compromise 的歷史紀錄較少</li>
</ol>
<p>跟 <a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0</a> 的關係：6.0 講「本機跑的 GGUF 模型供應鏈」、本章補「browser 跑的 client-side 模型供應鏈」— 兩種場景的 framing 一致、但具體威脅面跟工具不同。</p>
<h2 id="跟模組六的-routing">跟模組六的 routing</h2>
<p>本章資安段跟既有 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 的對應：</p>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>06 對應章節</th>
          <th>本章補的角度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模型 / 供應鏈信任</td>
          <td><a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0</a></td>
          <td>client-side 模型分發新形態</td>
      </tr>
      <tr>
          <td>Server 綁定</td>
          <td><a href="/blog/llm/06-security/inference-server-binding/" data-link-title="6.1 推論伺服器的綁定與暴露範圍" data-link-desc="個人 dev 場景下 llama-server / Ollama / LM Studio 的 bind address 判讀：127.0.0.1 vs LAN vs 反代、預設安全、誤開放給內網的後果">6.1</a></td>
          <td>靜態場景無 server、議題消失</td>
      </tr>
      <tr>
          <td>Tool use 權限</td>
          <td><a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2</a></td>
          <td>browser-side tool use（少數場景）</td>
      </tr>
      <tr>
          <td>Prompt injection</td>
          <td><a href="/blog/llm/06-security/prompt-injection-in-ide/" data-link-title="6.3 IDE 場景的 prompt injection" data-link-desc="個人 dev 場景下 IDE 寫 code 工作流的 prompt injection：codebase 內容、外部文件、剪貼簿作為攻擊面、跟雲端 LLM 場景的差異">6.3</a></td>
          <td>靜態 RAG 仍適用、source 變 web fetched</td>
      </tr>
      <tr>
          <td>跨雲端 / 本地資料邊界</td>
          <td><a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4</a></td>
          <td>靜態場景 query 走向跟 backend 場景不同</td>
      </tr>
      <tr>
          <td>Production routing</td>
          <td><a href="/blog/llm/06-security/routing-to-production-security/" data-link-title="6.5 跨進 production 的 routing 中樞" data-link-desc="個人 dev → 團隊 → production LLM 服務的三層演化、跟 backend/07 對應卡片的 routing 清單">6.5</a></td>
          <td>從個人靜態 RAG 升級到 production</td>
      </tr>
      <tr>
          <td><strong>API key 暴露 / browser</strong></td>
          <td>（無）</td>
          <td><strong>本章獨有</strong></td>
      </tr>
      <tr>
          <td><strong>CORS / 同源策略</strong></td>
          <td>（無）</td>
          <td><strong>本章獨有</strong></td>
      </tr>
      <tr>
          <td><strong>靜態場景 abuse / rate limit</strong></td>
          <td>（無、跟 6.1 server 議題不同）</td>
          <td><strong>本章獨有</strong></td>
      </tr>
  </tbody>
</table>
<h2 id="判讀流程">判讀流程</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">你的場景：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 有 backend？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  │    └─ 是 → 用 4.0 RAG + 4.8 embedding 主章節、本章不適用
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  │    └─ 否 → 繼續
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ 規模？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │    ├─ &lt; 1K chunks → 方案 1 純前端
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │    ├─ 1K-10K → 方案 1（embeddings.json ~ 40MB 仍可接受）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  │    ├─ 10K-100K → 方案 2 edge serverless
</span></span><span class="line"><span class="ln">10</span><span class="cl">  │    └─ 100K+ → 不再是靜態場景、回 backend
</span></span><span class="line"><span class="ln">11</span><span class="cl">  │
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ 需要 LLM 對話、不只 retrieval？
</span></span><span class="line"><span class="ln">13</span><span class="cl">  │    ├─ 是 + 隱私第一 → 方案 1 + WebLLM
</span></span><span class="line"><span class="ln">14</span><span class="cl">  │    ├─ 是 + 品質第一 → 方案 1 + user-key 或 方案 2
</span></span><span class="line"><span class="ln">15</span><span class="cl">  │    └─ 否（只要找文章） → 方案 4 純文字 search
</span></span><span class="line"><span class="ln">16</span><span class="cl">  │
</span></span><span class="line"><span class="ln">17</span><span class="cl">  └─ 預算 / vendor lock-in 容忍度？
</span></span><span class="line"><span class="ln">18</span><span class="cl">       ├─ 完全 zero-cost、無 vendor → 方案 1 純前端
</span></span><span class="line"><span class="ln">19</span><span class="cl">       ├─ 接受少量 cost、不想自己寫太多 → 方案 3 SaaS
</span></span><span class="line"><span class="ln">20</span><span class="cl">       └─ 接受少量 cost、想自己控 → 方案 2 edge</span></span></code></pre></div><h2 id="不在本章內的主題">不在本章內的主題</h2>
<ol>
<li><strong>完整 backend RAG</strong>：see <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a> 跟 <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model</a></li>
<li><strong>具體 SaaS API 教學</strong>：Algolia / Pinecone 等 API 細節隨版本變、見各 SaaS 文件</li>
<li><strong>WebGPU 內部細節</strong>：GPU shader、WebGPU API 設計屬 web platform 議題、不在 LLM 教材範圍</li>
<li><strong>Production 多租戶 RAG 服務</strong>：屬 backend/07、本章 framing 是「個人 / 小團隊靜態網站」</li>
<li><strong>企業合規 deployment</strong>：HIPAA / GDPR / SOC 2 跟具體 SaaS / cloud provider 強相關、見 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">backend/07 合規卡片</a> 跟 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 跨雲端</a></li>
</ol>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>四方案分類（純前端 / edge / SaaS / 純文字 search）</li>
<li>「靜態場景藏不了 secret」這個根本特性</li>
<li>API key 暴露 / CORS / abuse / 供應鏈 / 模型完整性 五大資安議題分類</li>
<li>跟 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 的 routing 關係</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 SaaS / edge provider（Cloudflare Vectorize / Pinecone / Algolia 等持續演化）</li>
<li>Client-side LLM runtime（WebLLM / wllama / transformers.js）的能力上限</li>
<li>WebGPU 支援度跟 browser 標準</li>
<li>哪些 LLM vendor 允許 browser 直 call（CORS 政策會變）</li>
<li>純文字 search 工具（Pagefind 等持續改進）</li>
</ul>
<h2 id="下一步">下一步</h2>
<p>本章是 <a href="/blog/llm/04-applications/" data-link-title="模組四：LLM 應用層原理" data-link-desc="Prompt 技術光譜、RAG、tool use、agent、應用層協議、人機協作、multi-agent、workflow 編排、eval 設計：跨工具不變的概念地圖">模組四</a> 最後一章。讀完整個模組四、完整覆蓋 LLM 作為系統元件的設計取捨。下一步可進入 <a href="/blog/llm/05-discrete-gpu/" data-link-title="模組五：Windows / Linux &#43; 獨立 GPU" data-link-desc="消費級 PC（Windows / Linux &#43; NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀、MoE CPU 卸載、KV cache 量化與 llama.cpp 調參">模組五 PC 獨立 GPU</a> 或 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六 安全</a> 補本地 dev 視角的安全議題。</p>
]]></content:encoded></item><item><title>Function Alias</title><link>https://tarrragon.github.io/blog/ci/knowledge-cards/function-alias/</link><pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/knowledge-cards/function-alias/</guid><description>&lt;p>Function Alias 的核心概念是「用穩定名稱指向不可變函式版本」。它讓 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/rollout-strategy/" data-link-title="Rollout Strategy" data-link-desc="說明新版本如何以可控節奏推進到全部流量">Rollout Strategy&lt;/a> 可以套用在 serverless function 上，並讓 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/rollback-strategy/" data-link-title="Rollback Strategy" data-link-desc="說明發布異常時如何快速回到已知可用狀態">Rollback Strategy&lt;/a> 具備快速切換入口。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Function Alias 位在 function version、traffic weight、event source 與 invocation entrypoint 之間，常見於 Lambda alias 或其他 serverless 平台的版本別名。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>新舊 function version 需要短暫共存。&lt;/li>
&lt;li>部分流量需要導向新版本做 canary。&lt;/li>
&lt;li>事故時需要把入口切回上一個版本。&lt;/li>
&lt;/ul>
&lt;h2 id="接近真實服務的例子">接近真實服務的例子&lt;/h2>
&lt;p>HTTP function 的 &lt;code>prod&lt;/code> alias 先把 5% 流量導向 version 42。若錯誤率穩定，逐步提高權重；若錯誤率升高，alias 切回 version 41。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Function Alias 要定義版本命名、流量權重、觀測指標、事件來源綁定與回復條件，讓函式發布具備可控入口。&lt;/p></description><content:encoded><![CDATA[<p>Function Alias 的核心概念是「用穩定名稱指向不可變函式版本」。它讓 <a href="/blog/ci/knowledge-cards/rollout-strategy/" data-link-title="Rollout Strategy" data-link-desc="說明新版本如何以可控節奏推進到全部流量">Rollout Strategy</a> 可以套用在 serverless function 上，並讓 <a href="/blog/ci/knowledge-cards/rollback-strategy/" data-link-title="Rollback Strategy" data-link-desc="說明發布異常時如何快速回到已知可用狀態">Rollback Strategy</a> 具備快速切換入口。</p>
<h2 id="概念位置">概念位置</h2>
<p>Function Alias 位在 function version、traffic weight、event source 與 invocation entrypoint 之間，常見於 Lambda alias 或其他 serverless 平台的版本別名。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>新舊 function version 需要短暫共存。</li>
<li>部分流量需要導向新版本做 canary。</li>
<li>事故時需要把入口切回上一個版本。</li>
</ul>
<h2 id="接近真實服務的例子">接近真實服務的例子</h2>
<p>HTTP function 的 <code>prod</code> alias 先把 5% 流量導向 version 42。若錯誤率穩定，逐步提高權重；若錯誤率升高，alias 切回 version 41。</p>
<h2 id="設計責任">設計責任</h2>
<p>Function Alias 要定義版本命名、流量權重、觀測指標、事件來源綁定與回復條件，讓函式發布具備可控入口。</p>
]]></content:encoded></item><item><title>Event Source</title><link>https://tarrragon.github.io/blog/ci/knowledge-cards/event-source/</link><pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/knowledge-cards/event-source/</guid><description>&lt;p>Event Source 的核心概念是「觸發執行的事件入口」。它決定 serverless function 或 worker 何時執行、如何重試、如何進入 dead-letter，並影響 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias&lt;/a> 的 rollout 與回復策略。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Event Source 位在 queue、topic、HTTP gateway、object storage、scheduler 與 function / worker 之間，負責把外部事件轉成執行請求。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>函式部署成功，但 invocation 因 trigger 設定失敗。&lt;/li>
&lt;li>Queue event 重試造成同一筆資料被重複處理。&lt;/li>
&lt;li>事件 schema 漂移導致 subscriber 解析失敗。&lt;/li>
&lt;/ul>
&lt;h2 id="接近真實服務的例子">接近真實服務的例子&lt;/h2>
&lt;p>Queue 觸發的 function 以 batch 方式處理訊息。新版本解析失敗時，訊息進入 dead-letter queue；團隊先停用 trigger，再修復 function 或重放事件。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Event Source 要定義 trigger 條件、batch size、retry、dead-letter、replay、權限與 schema 契約，讓事件驅動發布具備可觀測回復路徑。&lt;/p></description><content:encoded><![CDATA[<p>Event Source 的核心概念是「觸發執行的事件入口」。它決定 serverless function 或 worker 何時執行、如何重試、如何進入 dead-letter，並影響 <a href="/blog/ci/knowledge-cards/function-alias/" data-link-title="Function Alias" data-link-desc="說明 serverless function alias 如何把穩定入口指向特定版本並支援流量切換與回復">Function Alias</a> 的 rollout 與回復策略。</p>
<h2 id="概念位置">概念位置</h2>
<p>Event Source 位在 queue、topic、HTTP gateway、object storage、scheduler 與 function / worker 之間，負責把外部事件轉成執行請求。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>函式部署成功，但 invocation 因 trigger 設定失敗。</li>
<li>Queue event 重試造成同一筆資料被重複處理。</li>
<li>事件 schema 漂移導致 subscriber 解析失敗。</li>
</ul>
<h2 id="接近真實服務的例子">接近真實服務的例子</h2>
<p>Queue 觸發的 function 以 batch 方式處理訊息。新版本解析失敗時，訊息進入 dead-letter queue；團隊先停用 trigger，再修復 function 或重放事件。</p>
<h2 id="設計責任">設計責任</h2>
<p>Event Source 要定義 trigger 條件、batch size、retry、dead-letter、replay、權限與 schema 契約，讓事件驅動發布具備可觀測回復路徑。</p>
]]></content:encoded></item><item><title>Aurora Serverless v2 適用判斷：ACU 自動擴縮、混合 cluster 與何時不該用</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/aurora/serverless-v2-scaling/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/aurora/serverless-v2-scaling/</guid><description>&lt;p>Aurora Serverless v2 把 instance 的容量從「開機時固定的 instance class」改成「按負載秒級伸縮的 ACU」。它解的問題很具體：固定 provisioned cluster 在離峰時段付滿整台機器的錢、卻只用一小部分；尖峰來時又被 instance class 上限卡住。但 serverless v2 不是「比較便宜的 Aurora」——穩定高負載下它反而比同等 provisioned 貴。要不要用，取決於 workload 的負載形狀是否間歇、是否難預測。&lt;/p>
&lt;p>本文不是 Aurora overview（請看 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/aurora/" data-link-title="AWS Aurora" data-link-desc="AWS managed PostgreSQL / MySQL、storage / compute 分離、&amp;#43;75% 效能改善的 production 證據">Aurora vendor 頁&lt;/a>）— 而是 Serverless v2 的容量機制、設定與適用邊界的實作層教學。&lt;/p>
&lt;h2 id="核心機制acu-與秒級擴縮">核心機制：ACU 與秒級擴縮&lt;/h2>
&lt;p>Serverless v2 的容量單位是 ACU（Aurora Capacity Unit），一個 ACU 對應一組固定比例的記憶體與運算資源。cluster 不再綁定一個 instance class，而是設一個 ACU 區間（min / max），Aurora 依即時負載在區間內伸縮：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>屬性&lt;/th>
 &lt;th>Provisioned&lt;/th>
 &lt;th>Serverless v2&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>容量設定&lt;/td>
 &lt;td>固定 instance class（如 db.r6g.xlarge）&lt;/td>
 &lt;td>min / max ACU 區間&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>計費&lt;/td>
 &lt;td>按 instance 開機時數&lt;/td>
 &lt;td>按實際消耗的 ACU-秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴縮&lt;/td>
 &lt;td>手動改 instance class（有中斷）&lt;/td>
 &lt;td>秒級自動伸縮、無中斷&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>離峰成本&lt;/td>
 &lt;td>付滿整台&lt;/td>
 &lt;td>縮到 min ACU、只付低水位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適用負載&lt;/td>
 &lt;td>穩定、可預測&lt;/td>
 &lt;td>間歇、突發、難預測&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>擴縮行為&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>負載上升時 ACU 平滑增加、不需要切換 instance、無連線中斷&lt;/li>
&lt;li>負載下降時縮回低水位、但受 min ACU 下限約束&lt;/li>
&lt;li>min ACU 決定離峰的最低成本與「保留多少暖容量」；max ACU 決定尖峰的上限與成本天花板&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>Scope warning&lt;/strong>：「ACU 對應的記憶體比例」「serverless v2 是否能縮到 0」「最小 ACU 粒度」這些屬 AWS vendor 規格、會隨版本演進（auto-pause 等能力陸續調整）、實作時 cross-verify 官方 doc 當前值。本文不含 production case 揭露的 ACU 配置數字。&lt;/p>&lt;/blockquote>
&lt;p>對應 knowledge card：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">peak forecast&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">cost per request&lt;/a>。&lt;/p>
&lt;h2 id="min--max-acu-的設定權衡">min / max ACU 的設定權衡&lt;/h2>
&lt;p>min 與 max ACU 不是隨便設，兩端各自承擔不同風險。&lt;/p>
&lt;p>&lt;strong>min ACU 太低&lt;/strong>：離峰省錢，但流量回升時從很低的水位往上爬、爬升期間可能容量不足、且 buffer cache 在低 ACU 時被壓縮、回升後 cache 重新暖機、query latency 短暫升高。對延遲敏感、又有規律日週期的 workload，min ACU 不要壓到極限。&lt;/p>
&lt;p>&lt;strong>max ACU 太低&lt;/strong>：尖峰被天花板卡住、等同 provisioned 的 instance class 上限問題又回來。max ACU 要按「預期尖峰 + 餘量」設，並把它當成成本天花板來監控——max 設太高雖然不會平時就花錢，但失控 query（如缺索引的全表掃描）可能把 ACU 一路推到 max、帳單尖峰。&lt;/p>
&lt;p>&lt;strong>暖容量考量&lt;/strong>：min ACU 同時決定「保留多少隨時可用的暖容量」。完全不可預測、且要求第一個請求就低延遲的場景，min ACU 要留足暖機水位，不能為了省錢設到最低。&lt;/p>
&lt;h2 id="混合-clusterserverless--provisioned-並存">混合 cluster：serverless + provisioned 並存&lt;/h2>
&lt;p>Serverless v2 不是「整個 cluster 要嘛全 serverless、要嘛全 provisioned」。同一個 Aurora cluster 可以混用：writer 用 provisioned 保穩定、read replica 用 serverless v2 吸收讀取尖峰；或反過來。這讓 workload 的不同部分各取所需：&lt;/p></description><content:encoded><![CDATA[<p>Aurora Serverless v2 把 instance 的容量從「開機時固定的 instance class」改成「按負載秒級伸縮的 ACU」。它解的問題很具體：固定 provisioned cluster 在離峰時段付滿整台機器的錢、卻只用一小部分；尖峰來時又被 instance class 上限卡住。但 serverless v2 不是「比較便宜的 Aurora」——穩定高負載下它反而比同等 provisioned 貴。要不要用，取決於 workload 的負載形狀是否間歇、是否難預測。</p>
<p>本文不是 Aurora overview（請看 <a href="/blog/backend/01-database/vendors/aurora/" data-link-title="AWS Aurora" data-link-desc="AWS managed PostgreSQL / MySQL、storage / compute 分離、&#43;75% 效能改善的 production 證據">Aurora vendor 頁</a>）— 而是 Serverless v2 的容量機制、設定與適用邊界的實作層教學。</p>
<h2 id="核心機制acu-與秒級擴縮">核心機制：ACU 與秒級擴縮</h2>
<p>Serverless v2 的容量單位是 ACU（Aurora Capacity Unit），一個 ACU 對應一組固定比例的記憶體與運算資源。cluster 不再綁定一個 instance class，而是設一個 ACU 區間（min / max），Aurora 依即時負載在區間內伸縮：</p>
<table>
  <thead>
      <tr>
          <th>屬性</th>
          <th>Provisioned</th>
          <th>Serverless v2</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>容量設定</td>
          <td>固定 instance class（如 db.r6g.xlarge）</td>
          <td>min / max ACU 區間</td>
      </tr>
      <tr>
          <td>計費</td>
          <td>按 instance 開機時數</td>
          <td>按實際消耗的 ACU-秒</td>
      </tr>
      <tr>
          <td>擴縮</td>
          <td>手動改 instance class（有中斷）</td>
          <td>秒級自動伸縮、無中斷</td>
      </tr>
      <tr>
          <td>離峰成本</td>
          <td>付滿整台</td>
          <td>縮到 min ACU、只付低水位</td>
      </tr>
      <tr>
          <td>適用負載</td>
          <td>穩定、可預測</td>
          <td>間歇、突發、難預測</td>
      </tr>
  </tbody>
</table>
<p><strong>擴縮行為</strong>：</p>
<ul>
<li>負載上升時 ACU 平滑增加、不需要切換 instance、無連線中斷</li>
<li>負載下降時縮回低水位、但受 min ACU 下限約束</li>
<li>min ACU 決定離峰的最低成本與「保留多少暖容量」；max ACU 決定尖峰的上限與成本天花板</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：「ACU 對應的記憶體比例」「serverless v2 是否能縮到 0」「最小 ACU 粒度」這些屬 AWS vendor 規格、會隨版本演進（auto-pause 等能力陸續調整）、實作時 cross-verify 官方 doc 當前值。本文不含 production case 揭露的 ACU 配置數字。</p></blockquote>
<p>對應 knowledge card：<a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">peak forecast</a>、<a href="/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">cost per request</a>。</p>
<h2 id="min--max-acu-的設定權衡">min / max ACU 的設定權衡</h2>
<p>min 與 max ACU 不是隨便設，兩端各自承擔不同風險。</p>
<p><strong>min ACU 太低</strong>：離峰省錢，但流量回升時從很低的水位往上爬、爬升期間可能容量不足、且 buffer cache 在低 ACU 時被壓縮、回升後 cache 重新暖機、query latency 短暫升高。對延遲敏感、又有規律日週期的 workload，min ACU 不要壓到極限。</p>
<p><strong>max ACU 太低</strong>：尖峰被天花板卡住、等同 provisioned 的 instance class 上限問題又回來。max ACU 要按「預期尖峰 + 餘量」設，並把它當成成本天花板來監控——max 設太高雖然不會平時就花錢，但失控 query（如缺索引的全表掃描）可能把 ACU 一路推到 max、帳單尖峰。</p>
<p><strong>暖容量考量</strong>：min ACU 同時決定「保留多少隨時可用的暖容量」。完全不可預測、且要求第一個請求就低延遲的場景，min ACU 要留足暖機水位，不能為了省錢設到最低。</p>
<h2 id="混合-clusterserverless--provisioned-並存">混合 cluster：serverless + provisioned 並存</h2>
<p>Serverless v2 不是「整個 cluster 要嘛全 serverless、要嘛全 provisioned」。同一個 Aurora cluster 可以混用：writer 用 provisioned 保穩定、read replica 用 serverless v2 吸收讀取尖峰；或反過來。這讓 workload 的不同部分各取所需：</p>
<ul>
<li>穩定的寫入路徑用 provisioned instance、成本可預測</li>
<li>間歇的讀取分析、報表副本用 serverless v2、平時縮到低水位</li>
<li>failover 目標可指定 provisioned 或 serverless，依可用性需求</li>
</ul>
<p>混合配置的判讀是把 cluster 內每個角色當獨立的負載形狀評估，而非整個 cluster 一刀切。</p>
<h2 id="操作流程">操作流程</h2>
<p>從負載形狀評估到上線的 6 步流程。</p>
<h4 id="step-1判斷負載形狀">Step 1：判斷負載形狀</h4>
<p>用 CloudWatch 過去 30 天的 CPU / connection / IOPS，看負載是穩定平緩、規律日週期、還是不規則突發：</p>
<ul>
<li>穩定高負載（平均使用率高、波動小）→ provisioned 通常更划算</li>
<li>間歇 / 突發 / 開發測試 / 多租戶各自小 DB → serverless v2 適合</li>
<li>規律日週期（白天高晚上低）→ serverless v2 或 provisioned + scheduled 都可，算成本 crossover</li>
</ul>
<h4 id="step-2估-min--max-acu">Step 2：估 min / max ACU</h4>
<p>min 依離峰最低負載 + 暖容量需求；max 依尖峰負載 + 餘量。第一次設保守一點、上線後依實際 ACU 曲線收斂。</p>
<h4 id="step-3建立或轉換">Step 3：建立或轉換</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 新 cluster 指定 serverless v2 capacity range</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">aws rds create-db-cluster <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --db-cluster-identifier my-cluster <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --engine aurora-postgresql <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --serverless-v2-scaling-configuration <span class="nv">MinCapacity</span><span class="o">=</span>2,MaxCapacity<span class="o">=</span><span class="m">32</span></span></span></code></pre></div><p>既有 provisioned cluster 可加 serverless v2 reader、逐步驗證再調整 writer。</p>
<h4 id="step-4觀察-acu-曲線">Step 4：觀察 ACU 曲線</h4>
<p>上線後盯 <code>ServerlessDatabaseCapacity</code>（即時 ACU）與 <code>ACUUtilization</code>，確認伸縮符合負載、min/max 設定合理。</p>
<h4 id="step-5成本對照">Step 5：成本對照</h4>
<p>把實際 ACU-秒換算的帳單，跟「同等 provisioned instance 全時段開機」對照。若 serverless 帳單接近或超過 provisioned，代表負載其實夠穩定、該回 provisioned。</p>
<h4 id="step-6驗證點">Step 6：驗證點</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># 驗證離峰真的縮到 min ACU（看 ServerlessDatabaseCapacity 低谷）
</span></span><span class="line"><span class="ln">2</span><span class="cl"># 驗證尖峰沒撞 max ACU 天花板（看是否長時間貼著 max）
</span></span><span class="line"><span class="ln">3</span><span class="cl"># 驗證回升期 latency 可接受（min ACU 暖容量是否足夠）</span></span></code></pre></div><p><strong>Rollback boundary</strong>：serverless v2 與 provisioned 可互轉、reader 先轉驗證再動 writer；轉換本身有短暫中斷，要排 maintenance window。</p>
<h2 id="失敗模式">失敗模式</h2>
<p>production 常見的 5 個踩雷：</p>
<h4 id="case-1穩定高負載用-serverless-反而更貴">Case 1：穩定高負載用 serverless 反而更貴</h4>
<p>把一個 7x24 高使用率的 cluster 改 serverless「以為省錢」，實際 ACU 幾乎全時段貼近高水位、按 ACU-秒計費比固定 instance 貴。修法：穩定高負載用 provisioned；serverless 的省錢前提是「有顯著的離峰可以縮」。</p>
<h4 id="case-2min-acu-設太低回升期-latency-尖刺">Case 2：min ACU 設太低、回升期 latency 尖刺</h4>
<p>離峰縮到極低、早上流量回來時 cache 冷、ACU 從低水位爬、前幾分鐘 query 變慢。修法：規律日週期的 workload，min ACU 留足暖容量；或用 provisioned + scheduled scaling 處理可預測的日週期。</p>
<h4 id="case-3max-acu-沒當成本天花板監控">Case 3：max ACU 沒當成本天花板監控</h4>
<p>缺索引的 query 觸發全表掃描、ACU 一路衝到 max、帳單尖峰才發現。修法：max ACU 設合理上限 + CloudWatch alarm 盯 ACU 長時間貼 max（那是 query 或容量問題的訊號，不是正常擴縮）。</p>
<h4 id="case-4把-serverless-當不用做容量規劃">Case 4：把 serverless 當「不用做容量規劃」</h4>
<p>以為 serverless 自動伸縮就不必估容量、min/max 隨便設。修法：serverless 改變的是「不用手動切 instance」，不是「不用理解負載形狀」；min/max 仍要基於負載曲線設定。</p>
<h4 id="case-5對延遲極敏感的-oltp-全-serverless">Case 5：對延遲極敏感的 OLTP 全 serverless</h4>
<p>核心交易路徑要求穩定低延遲、卻用會伸縮的 serverless writer、伸縮邊界期間 latency 抖動。修法：穩定低延遲的核心寫入用 provisioned writer，serverless 留給可容忍伸縮抖動的讀取 / 分析副本（混合 cluster）。</p>
<p><strong>Anti-recommendation</strong>：負載穩定、使用率長期偏高、或對延遲抖動零容忍的核心 OLTP → 用 provisioned；serverless v2 的價值在「間歇、突發、難預測、或有大量離峰」的負載，沒有離峰可縮就沒有省錢空間。</p>
<h2 id="容量與觀測">容量與觀測</h2>
<p>CloudWatch metric：</p>
<ul>
<li><code>ServerlessDatabaseCapacity</code>：即時 ACU、看伸縮曲線</li>
<li><code>ACUUtilization</code>：ACU 使用率、判斷 min/max 設定是否合理</li>
<li><code>CPUUtilization</code> / <code>DatabaseConnections</code>：底層負載、對照 ACU 是否跟得上</li>
</ul>
<p><strong>判讀</strong>：</p>
<ul>
<li>ACU 長時間貼近 max → max 設太低或有失控 query，要查</li>
<li>ACU 長時間貼近 min 且使用率低 → 負載其實很輕，min 可能可再降、或這個 cluster 適合更小配置</li>
<li>ACU 幾乎不波動且水位高 → 負載穩定，serverless 沒發揮價值，評估改 provisioned</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：本文未引用 production case 的 ACU 數字；上述 metric 與判讀屬 vendor 規格 + 通用容量工程。</p></blockquote>
<p>接回 <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>、<a href="/blog/backend/01-database/vendors/aurora/" data-link-title="AWS Aurora" data-link-desc="AWS managed PostgreSQL / MySQL、storage / compute 分離、&#43;75% 效能改善的 production 證據">Aurora 容量規劃要點</a>。</p>
<h2 id="邊界與整合">邊界與整合</h2>
<h3 id="serverless-v2-vs-provisioned--scheduled-scaling">Serverless v2 vs provisioned + scheduled scaling</h3>
<p>兩者都能處理「負載隨時間變」，但適用場景不同：</p>
<ul>
<li><strong>scheduled scaling（provisioned）</strong>：負載 <em>可預測</em>（已知的日週期、已知大活動）→ 預先排程改容量，成本最可控</li>
<li><strong>serverless v2</strong>：負載 <em>不可預測</em>（突發、不規則）→ 自動伸縮吸收，不需預測</li>
</ul>
<p>可預測的尖峰用 scheduled、不可預測的用 serverless，這跟 <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB capacity mode</a> 的 predictable-peak vs flash-sale 判讀同源。</p>
<h3 id="sibling-與-cross-link">Sibling 與 cross-link</h3>
<ul>
<li><a href="/blog/backend/01-database/vendors/aurora/storage-architecture/" data-link-title="Aurora Storage Architecture：quorum-based 分散式 log 與韌性即性能設計" data-link-desc="Aurora storage / compute 分離、6-way 跨 AZ replication、4-of-6 write / 3-of-6 read quorum、韌性投資自動 amortize 成 read 性能、DraftKings 6ms 寫 / &lt;1ms 讀 production reference">storage-architecture</a> — serverless 只改 compute 層容量、storage 層 quorum 設計不變</li>
<li><a href="/blog/backend/01-database/vendors/aurora/read-replica-scaling/" data-link-title="Aurora Read Replica Scaling：15 replica 上限、lag profile、headroom 預留與 fleet 治理" data-link-desc="Aurora 15 replica 上限、共享 storage 為什麼能養大量 replica、事件型容量分級表、DraftKings headroom 預留判讀、FanDuel 雙 SLO 並行、fleet 治理 3 條 driver（business sharding / microservice / 合規）">read-replica-scaling</a> — serverless reader 吸收讀取尖峰、與 fleet 治理結合</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/aurora-io-optimized-cost/" data-link-title="Aurora PostgreSQL I/O-Optimized Cost" data-link-desc="Aurora PostgreSQL Standard 與 I/O-Optimized 的成本模型、I/O 壓力、workload 判斷、遷移與回退條件">Aurora I/O-Optimized cost</a> — serverless 算的是 compute（ACU）成本、I/O-Optimized 算的是 storage I/O 成本，兩個成本軸獨立、要分開評估</li>
<li><a href="/blog/backend/01-database/vendors/aurora/rds-proxy-connection-pooling/" data-link-title="Aurora RDS Proxy 與連線管理：connection multiplexing、pinning 陷阱與 failover 加速" data-link-desc="RDS Proxy 不是「連上去就自動省連線」；本文展開 connection multiplexing 機制、哪些 session 操作會觸發 pinning 讓 multiplexing 失效、failover 期間 proxy 如何保持 client 連線縮短中斷，以及 RDS Proxy 與自管 pgbouncer 的責任切分">rds-proxy-connection-pooling</a> — serverless + Lambda 場景的連線管理</li>
<li>替代路由：負載穩定且高 → provisioned；KV access pattern → <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></li>
<li>跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">Netflix 9.C23</a> 互引：polyglot 架構下不同 workload 用不同 Aurora 配置（穩定 OLTP provisioned、間歇副本 serverless）</li>
</ul>
]]></content:encoded></item><item><title>Aurora RDS Proxy 與連線管理：connection multiplexing、pinning 陷阱與 failover 加速</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/aurora/rds-proxy-connection-pooling/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/aurora/rds-proxy-connection-pooling/</guid><description>&lt;p>Lambda 函式在流量尖峰被同時拉起幾百個實例、每個各自開一條到 Aurora 的連線、Aurora 的 connection 上限瞬間被打爆、新請求拿不到連線、整批失敗。根因是 &lt;em>連線管理&lt;/em> 缺位、Aurora 容量本身夠用——serverless 與高並發短連線 workload 製造的連線數遠超過資料庫該同時維持的後端連線。RDS Proxy 在 application 與 Aurora 之間做 connection multiplexing，把大量 client 連線收斂成少量後端連線。但它不是「連上去就自動省」——某些 session 操作會讓連線被 pin 住、multiplexing 失效。&lt;/p>
&lt;p>本文不是 Aurora overview（請看 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/aurora/" data-link-title="AWS Aurora" data-link-desc="AWS managed PostgreSQL / MySQL、storage / compute 分離、&amp;#43;75% 效能改善的 production 證據">Aurora vendor 頁&lt;/a>）— 而是 RDS Proxy 連線管理機制與陷阱的實作層教學。&lt;/p>
&lt;h2 id="核心機制connection-multiplexing">核心機制：connection multiplexing&lt;/h2>
&lt;p>RDS Proxy 維護一個到 Aurora 的後端連線池，多個 client 連線共享這些後端連線。當 client 連線閒置（交易之間沒有活動），proxy 可以把對應的後端連線釋放回池子給其他 client 用：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>沒有 proxy&lt;/th>
 &lt;th>有 RDS Proxy&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>每個 client 連線 = 一條後端連線&lt;/td>
 &lt;td>多個 client 連線共享少量後端連線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lambda 並發 N → 後端 N 條連線&lt;/td>
 &lt;td>Lambda 並發 N → 後端遠少於 N 條&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>failover 時 client 連線斷、要重連&lt;/td>
 &lt;td>proxy 保持 client 連線、後端切換對 client 透明&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>連線建立開銷由 application 承擔&lt;/td>
 &lt;td>proxy 維持暖連線池、省去反覆建立&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>multiplexing 生效的前提是 client 連線「閒置時可以被借走」。這只在連線處於 &lt;em>交易之間&lt;/em> 的乾淨狀態時成立——一旦連線帶了交易內狀態，proxy 不能把它借給別人，這就是 pinning。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Scope warning&lt;/strong>：「RDS Proxy 支援的 engine / 連線數上限 / IAM 認證細節」屬 AWS vendor 規格、實作時 cross-verify 官方 doc 當前值。本文不含 production case 揭露的 proxy 配置數字。&lt;/p>&lt;/blockquote>
&lt;p>對應 knowledge card：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool&lt;/a>。&lt;/p>
&lt;h2 id="pinningmultiplexing-失效的主因">Pinning：multiplexing 失效的主因&lt;/h2>
&lt;p>Pinning 是 RDS Proxy 最常被忽略、卻直接決定省連線效果的機制。當 client 在連線上做了「跨交易持續的 session 狀態」操作，proxy 無法安全地把這條後端連線借給其他 client，於是把它 &lt;em>pin&lt;/em>（綁定）到該 client 直到連線關閉——這條後端連線在 pin 期間不參與 multiplexing。&lt;/p>
&lt;p>常見觸發 pinning 的操作：&lt;/p>
&lt;ul>
&lt;li>session 層級的變數設定（&lt;code>SET&lt;/code> 某些 session variable）&lt;/li>
&lt;li>建立 temp table&lt;/li>
&lt;li>prepared statement（某些情況）&lt;/li>
&lt;li>advisory lock、保持開啟的交易&lt;/li>
&lt;li>部分 session 層級的設定語句&lt;/li>
&lt;/ul>
&lt;p>pinning 的後果是「明明裝了 RDS Proxy、後端連線數卻沒降下來」。若大量 client 都觸發 pinning，等於退化回「一個 client 一條後端連線」、proxy 白裝。&lt;/p>
&lt;p>&lt;strong>判讀與修法方向&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>監控 &lt;code>DatabaseConnectionsCurrentlySessionPinned&lt;/code>，看 pinning 比例&lt;/li>
&lt;li>application 端避免不必要的 session 狀態（少用 session variable、temp table；改用交易內可清理的方式）&lt;/li>
&lt;li>真的需要 session 狀態的 workload，接受該連線會 pin、或評估這類 workload 是否適合走 proxy&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>Scope warning&lt;/strong>：「哪些具體語句觸發 pinning」隨 RDS Proxy 版本與 engine 演進、實作時以 AWS doc 當前清單為準；本段列舉是常見類型、非完整或固定清單。&lt;/p></description><content:encoded><![CDATA[<p>Lambda 函式在流量尖峰被同時拉起幾百個實例、每個各自開一條到 Aurora 的連線、Aurora 的 connection 上限瞬間被打爆、新請求拿不到連線、整批失敗。根因是 <em>連線管理</em> 缺位、Aurora 容量本身夠用——serverless 與高並發短連線 workload 製造的連線數遠超過資料庫該同時維持的後端連線。RDS Proxy 在 application 與 Aurora 之間做 connection multiplexing，把大量 client 連線收斂成少量後端連線。但它不是「連上去就自動省」——某些 session 操作會讓連線被 pin 住、multiplexing 失效。</p>
<p>本文不是 Aurora overview（請看 <a href="/blog/backend/01-database/vendors/aurora/" data-link-title="AWS Aurora" data-link-desc="AWS managed PostgreSQL / MySQL、storage / compute 分離、&#43;75% 效能改善的 production 證據">Aurora vendor 頁</a>）— 而是 RDS Proxy 連線管理機制與陷阱的實作層教學。</p>
<h2 id="核心機制connection-multiplexing">核心機制：connection multiplexing</h2>
<p>RDS Proxy 維護一個到 Aurora 的後端連線池，多個 client 連線共享這些後端連線。當 client 連線閒置（交易之間沒有活動），proxy 可以把對應的後端連線釋放回池子給其他 client 用：</p>
<table>
  <thead>
      <tr>
          <th>沒有 proxy</th>
          <th>有 RDS Proxy</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每個 client 連線 = 一條後端連線</td>
          <td>多個 client 連線共享少量後端連線</td>
      </tr>
      <tr>
          <td>Lambda 並發 N → 後端 N 條連線</td>
          <td>Lambda 並發 N → 後端遠少於 N 條</td>
      </tr>
      <tr>
          <td>failover 時 client 連線斷、要重連</td>
          <td>proxy 保持 client 連線、後端切換對 client 透明</td>
      </tr>
      <tr>
          <td>連線建立開銷由 application 承擔</td>
          <td>proxy 維持暖連線池、省去反覆建立</td>
      </tr>
  </tbody>
</table>
<p>multiplexing 生效的前提是 client 連線「閒置時可以被借走」。這只在連線處於 <em>交易之間</em> 的乾淨狀態時成立——一旦連線帶了交易內狀態，proxy 不能把它借給別人，這就是 pinning。</p>
<blockquote>
<p><strong>Scope warning</strong>：「RDS Proxy 支援的 engine / 連線數上限 / IAM 認證細節」屬 AWS vendor 規格、實作時 cross-verify 官方 doc 當前值。本文不含 production case 揭露的 proxy 配置數字。</p></blockquote>
<p>對應 knowledge card：<a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a>。</p>
<h2 id="pinningmultiplexing-失效的主因">Pinning：multiplexing 失效的主因</h2>
<p>Pinning 是 RDS Proxy 最常被忽略、卻直接決定省連線效果的機制。當 client 在連線上做了「跨交易持續的 session 狀態」操作，proxy 無法安全地把這條後端連線借給其他 client，於是把它 <em>pin</em>（綁定）到該 client 直到連線關閉——這條後端連線在 pin 期間不參與 multiplexing。</p>
<p>常見觸發 pinning 的操作：</p>
<ul>
<li>session 層級的變數設定（<code>SET</code> 某些 session variable）</li>
<li>建立 temp table</li>
<li>prepared statement（某些情況）</li>
<li>advisory lock、保持開啟的交易</li>
<li>部分 session 層級的設定語句</li>
</ul>
<p>pinning 的後果是「明明裝了 RDS Proxy、後端連線數卻沒降下來」。若大量 client 都觸發 pinning，等於退化回「一個 client 一條後端連線」、proxy 白裝。</p>
<p><strong>判讀與修法方向</strong>：</p>
<ul>
<li>監控 <code>DatabaseConnectionsCurrentlySessionPinned</code>，看 pinning 比例</li>
<li>application 端避免不必要的 session 狀態（少用 session variable、temp table；改用交易內可清理的方式）</li>
<li>真的需要 session 狀態的 workload，接受該連線會 pin、或評估這類 workload 是否適合走 proxy</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：「哪些具體語句觸發 pinning」隨 RDS Proxy 版本與 engine 演進、實作時以 AWS doc 當前清單為準；本段列舉是常見類型、非完整或固定清單。</p></blockquote>
<h2 id="failover-加速">Failover 加速</h2>
<p>RDS Proxy 的第二個價值是縮短 failover 對 application 的中斷。沒有 proxy 時，writer failover 會讓所有 client 連線斷掉、application 要偵測、重連、重建連線池；有 proxy 時，proxy 保持與 client 的連線、在後端把流量切到新 writer，client 端感知到的中斷時間縮短。</p>
<p>這對連線建立成本高、或 failover 期間不能大量重連的 workload 特別有價值。但 proxy 不消除 failover 本身——in-flight 的交易仍會失敗、application 仍要有 retry；proxy 縮短的是「重建連線」這段，不是「交易不中斷」。</p>
<h2 id="操作流程">操作流程</h2>
<p>從連線壓力判讀到上線的 6 步流程。</p>
<h4 id="step-1確認是不是連線問題">Step 1：確認是不是連線問題</h4>
<p>先區分「Aurora 容量不夠」vs「連線管理問題」。看 <code>DatabaseConnections</code> 是否逼近上限、且 CPU/IOPS 還有餘量——後者是典型的連線數問題、proxy 能解；若是 CPU/IOPS 飽和，proxy 不解。</p>
<h4 id="step-2判斷-workload-是否適合-proxy">Step 2：判斷 workload 是否適合 proxy</h4>
<ul>
<li>serverless / Lambda / 高並發短連線 → 適合（連線爆炸是主問題）</li>
<li>少量長連線、穩定的 application server → proxy 效益有限（連線數本就可控）</li>
<li>大量 session 狀態 workload → pinning 會吃掉 multiplexing 效益、要先評估</li>
</ul>
<h4 id="step-3建立-proxy">Step 3：建立 proxy</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">aws rds create-db-proxy <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --db-proxy-name my-aurora-proxy <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --engine-family POSTGRESQL <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --auth ... <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --role-arn ... <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --vpc-subnet-ids ...</span></span></code></pre></div><p>application 連到 proxy endpoint 而非直連 cluster endpoint。</p>
<h4 id="step-4減少-pinning">Step 4：減少 pinning</h4>
<p>review application 的 session 狀態使用、移除不必要的 <code>SET</code> / temp table；連線池設定避免長時間持有閒置連線。</p>
<h4 id="step-5驗證-multiplexing-生效">Step 5：驗證 multiplexing 生效</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># 對照後端連線數：裝 proxy 後 Aurora 的 DatabaseConnections 應顯著低於 client 並發數
</span></span><span class="line"><span class="ln">2</span><span class="cl"># 看 DatabaseConnectionsCurrentlySessionPinned：pinning 比例高代表 multiplexing 沒發揮</span></span></code></pre></div><h4 id="step-6驗證-failover-行為">Step 6：驗證 failover 行為</h4>
<p>主動觸發一次 failover、測量 application 感知到的中斷時間、確認 retry 邏輯能吸收 in-flight 交易失敗。</p>
<p><strong>Rollback boundary</strong>：application 可在 proxy endpoint 與直連 cluster endpoint 間切換、proxy 出問題時改回直連（但直連會回到連線爆炸風險，要先確認後端撐得住）。</p>
<h2 id="失敗模式">失敗模式</h2>
<p>production 常見的 5 個踩雷：</p>
<h4 id="case-1裝了-proxy-但-pinning-比例高連線沒降">Case 1：裝了 proxy 但 pinning 比例高、連線沒降</h4>
<p>application 大量用 session variable / temp table、多數連線被 pin、後端連線數沒降、proxy 白裝。修法：監控 pinning 比例、減少 session 狀態；理解 proxy 的省連線前提是連線可被借走。</p>
<h4 id="case-2把-proxy-當aurora-容量擴充">Case 2：把 proxy 當「Aurora 容量擴充」</h4>
<p>連線數沒問題、是 CPU/IOPS 飽和、卻裝 proxy 期待變快。修法：proxy 解連線管理、不解運算容量；容量問題要擴 instance / 加 replica。</p>
<h4 id="case-3以為-proxy-讓-failover-零中斷">Case 3：以為 proxy 讓 failover 零中斷</h4>
<p>裝了 proxy 就拿掉 application 的 retry、failover 時 in-flight 交易失敗沒處理。修法：proxy 縮短重連時間、不保證交易不中斷；application 仍要 retry in-flight 交易。</p>
<h4 id="case-4少量長連線-workload-強裝-proxy">Case 4：少量長連線 workload 強裝 proxy</h4>
<p>穩定的 application server 連線數本就可控、裝 proxy 多一跳延遲、效益有限。修法：proxy 的價值在連線爆炸場景（serverless / 高並發短連線）；連線可控的 workload 不必加。</p>
<h4 id="case-5proxy-與自管-pooler-疊加未理清責任">Case 5：proxy 與自管 pooler 疊加未理清責任</h4>
<p>application 已有自管連線池（如語言層 pool）、又加 RDS Proxy、兩層 pool 互相打架、連線數行為難預測。修法：理清兩層職責——application 層 pool 管「app 到 proxy」、proxy 管「proxy 到 Aurora」；兩層設定要協調、不是各設各的。</p>
<p><strong>Anti-recommendation</strong>：連線數本就可控的少量長連線 workload、或 workload 大量依賴 session 狀態（pinning 會吃掉效益）→ 不必上 RDS Proxy；它的價值集中在 serverless / Lambda / 高並發短連線的連線爆炸場景。</p>
<h2 id="容量與觀測">容量與觀測</h2>
<p>CloudWatch metric：</p>
<ul>
<li><code>DatabaseConnections</code>（Aurora 端）：裝 proxy 後應顯著低於 client 並發數</li>
<li><code>DatabaseConnectionsCurrentlySessionPinned</code>：pinning 數、判斷 multiplexing 效益</li>
<li><code>ClientConnections</code>（proxy 端）：client 側連線數、對照後端收斂比例</li>
<li><code>QueryDatabaseResponseLatency</code>：proxy 多一跳的延遲影響</li>
</ul>
<p><strong>判讀</strong>：</p>
<ul>
<li>後端連線數沒因 proxy 下降 → pinning 比例高或 workload 不適合</li>
<li>pinning 數持續高 → application session 狀態過多、需 review</li>
<li>proxy 延遲明顯 → 評估這一跳對延遲敏感路徑是否值得</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：本文未引用 production case 的 proxy metric 數字；上述指標與判讀屬 vendor 規格 + 通用連線管理工程。</p></blockquote>
<p>接回 <a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a>、<a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1 高併發下的 SQL 讀寫邊界</a>。</p>
<h2 id="邊界與整合">邊界與整合</h2>
<h3 id="rds-proxy-vs-自管-pgbouncer">RDS Proxy vs 自管 pgbouncer</h3>
<p>兩者都是 connection pooler，責任切分在「managed vs 自管」：</p>
<ul>
<li><strong>RDS Proxy</strong>：AWS managed、跟 Aurora / IAM / Secrets Manager 整合、零運維、含 failover 加速；綁 AWS</li>
<li><strong>自管 pgbouncer / pgcat</strong>：自己部署運維、pooling 模式（session / transaction / statement）可細調、跨雲可攜；運維責任自負</li>
</ul>
<p>PostgreSQL 的通用連線池機制與 pgbouncer 細節主寫於 <a href="/blog/backend/01-database/vendors/postgresql/pgbouncer-config/" data-link-title="PostgreSQL pgBouncer 配置 &#43; 連線池治理" data-link-desc="pgBouncer transaction pooling 配置、跟 application connection pool 的分層、production 故障演練（pool exhaustion / stale connection / DNS failover）跟容量規劃">pgbouncer-config</a> 與 <a href="/blog/backend/01-database/vendors/postgresql/connection-pooler-comparison/" data-link-title="PostgreSQL Connection Pooler Comparison" data-link-desc="PostgreSQL PgBouncer、Odyssey、RDS Proxy、application pool 與 transaction pooling 的選型比較">connection-pooler-comparison</a>；本篇聚焦 RDS Proxy 這個 AWS managed 方案的機制與 pinning 陷阱。要細調 pooling 模式、或需要跨雲可攜 → 評估自管 pooler；要零運維 + Aurora 原生整合 + failover 加速 → RDS Proxy。</p>
<h3 id="sibling-與-cross-link">Sibling 與 cross-link</h3>
<ul>
<li><a href="/blog/backend/01-database/vendors/aurora/serverless-v2-scaling/" data-link-title="Aurora Serverless v2 適用判斷：ACU 自動擴縮、混合 cluster 與何時不該用" data-link-desc="Aurora Serverless v2 不是「比較便宜的 Aurora」；本文展開 ACU 計費粒度、秒級自動擴縮機制、min/max ACU 設定、serverless 與 provisioned 同 cluster 混用，以及穩定高負載下 serverless 反而更貴的成本 crossover 邊界">serverless-v2-scaling</a> — serverless + Lambda 場景的連線管理常與 RDS Proxy 一起出現</li>
<li><a href="/blog/backend/01-database/vendors/aurora/cross-az-failover-rto/" data-link-title="Aurora Cross-AZ Failover：RTO 量測、endpoint routing 與 application reconnect 契約" data-link-desc="Aurora cross-AZ failover lifecycle（detection / promotion / DNS update）、&lt; 30 秒 RTO、application DNS cache 跟 connection pool 對齊、Standard Chartered 受監管場景為什麼用獨立 cluster 而非 Global Database failover">cross-az-failover-rto</a> — proxy 縮短 failover 重連時間、與 RTO 目標結合</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/pgbouncer-config/" data-link-title="PostgreSQL pgBouncer 配置 &#43; 連線池治理" data-link-desc="pgBouncer transaction pooling 配置、跟 application connection pool 的分層、production 故障演練（pool exhaustion / stale connection / DNS failover）跟容量規劃">pgbouncer-config</a> / <a href="/blog/backend/01-database/vendors/postgresql/connection-pooler-comparison/" data-link-title="PostgreSQL Connection Pooler Comparison" data-link-desc="PostgreSQL PgBouncer、Odyssey、RDS Proxy、application pool 與 transaction pooling 的選型比較">connection-pooler-comparison</a> — 通用連線池 SSoT、自管方案對照</li>
<li><a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1 高併發下的 SQL 讀寫邊界</a> — 連線池與 transaction 範圍控制</li>
<li>替代路由：需要細調 pooling 模式 / 跨雲 → 自管 pgbouncer</li>
</ul>
]]></content:encoded></item><item><title>CockroachDB Cloud Serverless 適用判斷：按用量 vs dedicated 的取捨與 RU 計費結構</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/cockroachdb/cloud-serverless/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/cockroachdb/cloud-serverless/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor overview&lt;/a> 的 implementation-layer deep article。寫作參照 &lt;a href="https://tarrragon.github.io/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology&lt;/a>。本文聚焦 &lt;em>Cockroach Cloud serverless 與 dedicated 的取捨判讀、RU 計費結構、冷啟動 / scale 行為、何時用 serverless&lt;/em>。Self-managed 規模化的運維責任（Netflix Platform Team 養 380+ cluster）跟賽季型擴縮（Hard Rock 100 ↔ 33 node）作為 &lt;em>對照軸&lt;/em> 引用、不重展 self-host 運維細節。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="問題情境要-managed-cockroachdb但-serverless-跟-dedicated-該選哪個">問題情境：要 managed CockroachDB、但 serverless 跟 dedicated 該選哪個&lt;/h2>
&lt;p>團隊決定不自管 Raft / backup / upgrade，改走 Cockroach Cloud managed，接著面對的是 serverless 跟 dedicated 兩種 managed 形態的取捨。這個取捨不是「哪個比較好」，而是 &lt;em>容量壓力的形狀對應哪種計費與 scale 模型&lt;/em>。&lt;/p>
&lt;p>Cockroach Cloud serverless 是 &lt;em>把容量決策從「預先 provision 節點」換成「按實際用量計費 + 自動 scale」&lt;/em> 的 managed 形態。它消去了 cluster sizing 這個決策 — 沒有「要開幾個 node」的問題，資源隨 workload 自動伸縮，甚至閒置時 scale 到接近零。代價是計費單位變成抽象的 Request Unit（RU），用量暴衝時成本跟著暴衝，且共享底層資源帶來冷啟動與性能可預測性的取捨。&lt;/p>
&lt;p>dedicated 則保留 &lt;em>固定的 cluster 容量 + 可預測的計費&lt;/em>，由 vendor 代管運維但容量仍是團隊決策。&lt;/p>
&lt;p>讀者進來最常卡的三題：&lt;/p>
&lt;ul>
&lt;li>serverless 的 RU 計費到底計什麼、怎麼估自己的 workload 會花多少？&lt;/li>
&lt;li>serverless 閒置會 scale 到零，那冷啟動會不會讓第一個請求變慢？&lt;/li>
&lt;li>什麼 workload 適合 serverless、什麼時候該選 dedicated 或乾脆 self-managed？&lt;/li>
&lt;/ul>
&lt;p>這三題的共同核心是 &lt;em>把 workload 的流量形狀（穩定 vs 突發、可預測 vs 不可預測、高峰 vs 長尾）翻譯成計費與 scale 模型&lt;/em>。&lt;/p>
&lt;p>問題情境的對照 trigger 來自兩個 self-managed 規模的 case，它們界定了「什麼時候 serverless / dedicated 都不對、要 self-host」的邊界。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/" data-link-title="9.C40 Netflix：380&amp;#43; CockroachDB cluster 的 multi-active 拓樸艦隊" data-link-desc="Netflix 把 Cassandra 不夠用的 transactional workload 移到 CockroachDB、380&amp;#43; cluster / 60&amp;#43; 跨 region、含 Open Connect、studio cloud drive、gaming control plane">9.C40 Netflix&lt;/a> 是 self-managed 380+ cluster（case 揭露 380+ 為含非 production 的總數、production cluster 160+），case 明確揭露這需要 &lt;em>專屬 Database Platform Team&lt;/em>（backup、upgrade、incident response、capacity review），並警示「沒這量級團隊就走 Cockroach Cloud managed、不要 self-host」。這條判讀的反向就是本文的入口 — 大多數團隊沒有 Platform Team，managed 才是合理起點，問題只剩 serverless 還是 dedicated。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor overview</a> 的 implementation-layer deep article。寫作參照 <a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology</a>。本文聚焦 <em>Cockroach Cloud serverless 與 dedicated 的取捨判讀、RU 計費結構、冷啟動 / scale 行為、何時用 serverless</em>。Self-managed 規模化的運維責任（Netflix Platform Team 養 380+ cluster）跟賽季型擴縮（Hard Rock 100 ↔ 33 node）作為 <em>對照軸</em> 引用、不重展 self-host 運維細節。</p></blockquote>
<hr>
<h2 id="問題情境要-managed-cockroachdb但-serverless-跟-dedicated-該選哪個">問題情境：要 managed CockroachDB、但 serverless 跟 dedicated 該選哪個</h2>
<p>團隊決定不自管 Raft / backup / upgrade，改走 Cockroach Cloud managed，接著面對的是 serverless 跟 dedicated 兩種 managed 形態的取捨。這個取捨不是「哪個比較好」，而是 <em>容量壓力的形狀對應哪種計費與 scale 模型</em>。</p>
<p>Cockroach Cloud serverless 是 <em>把容量決策從「預先 provision 節點」換成「按實際用量計費 + 自動 scale」</em> 的 managed 形態。它消去了 cluster sizing 這個決策 — 沒有「要開幾個 node」的問題，資源隨 workload 自動伸縮，甚至閒置時 scale 到接近零。代價是計費單位變成抽象的 Request Unit（RU），用量暴衝時成本跟著暴衝，且共享底層資源帶來冷啟動與性能可預測性的取捨。</p>
<p>dedicated 則保留 <em>固定的 cluster 容量 + 可預測的計費</em>，由 vendor 代管運維但容量仍是團隊決策。</p>
<p>讀者進來最常卡的三題：</p>
<ul>
<li>serverless 的 RU 計費到底計什麼、怎麼估自己的 workload 會花多少？</li>
<li>serverless 閒置會 scale 到零，那冷啟動會不會讓第一個請求變慢？</li>
<li>什麼 workload 適合 serverless、什麼時候該選 dedicated 或乾脆 self-managed？</li>
</ul>
<p>這三題的共同核心是 <em>把 workload 的流量形狀（穩定 vs 突發、可預測 vs 不可預測、高峰 vs 長尾）翻譯成計費與 scale 模型</em>。</p>
<p>問題情境的對照 trigger 來自兩個 self-managed 規模的 case，它們界定了「什麼時候 serverless / dedicated 都不對、要 self-host」的邊界。</p>
<p><a href="/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/" data-link-title="9.C40 Netflix：380&#43; CockroachDB cluster 的 multi-active 拓樸艦隊" data-link-desc="Netflix 把 Cassandra 不夠用的 transactional workload 移到 CockroachDB、380&#43; cluster / 60&#43; 跨 region、含 Open Connect、studio cloud drive、gaming control plane">9.C40 Netflix</a> 是 self-managed 380+ cluster（case 揭露 380+ 為含非 production 的總數、production cluster 160+），case 明確揭露這需要 <em>專屬 Database Platform Team</em>（backup、upgrade、incident response、capacity review），並警示「沒這量級團隊就走 Cockroach Cloud managed、不要 self-host」。這條判讀的反向就是本文的入口 — 大多數團隊沒有 Platform Team，managed 才是合理起點，問題只剩 serverless 還是 dedicated。</p>
<p><a href="/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/" data-link-title="9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 &#43; 跨州單一邏輯 DB" data-link-desc="Hard Rock Digital 用 CockroachDB 跨 AWS Outposts &#43; US-East-1、Wire Act 強制資料留州、單一邏輯 DB 解多州 sportsbook、100 node 32 vCPU 撐 Super Bowl">9.C41 Hard Rock Digital</a> 是 self-managed、賽季型擴縮（高峰 ~100 node、淡季 ~33 node，case 觀察段揭露）。這個 100 ↔ 33 的擺盪是 <em>已知時間點的年度循環</em>（NFL / NBA 賽季切換），不是不可預測的突發。case 還揭露合規驅動需要 AWS Outposts 把運算放進州內 — 這把它鎖死在 self-managed。Hard Rock 的形狀正好對照出 serverless 的適配範圍：serverless 擅長 <em>不可預測</em> 的突發與長尾閒置，而非 <em>可預測且需要特定部署位置</em> 的賽季擴縮。</p>
<h2 id="核心機制ru-計費--自動-scale--冷啟動">核心機制：RU 計費 + 自動 scale + 冷啟動</h2>
<h3 id="request-unit把多維資源用量折算成單一計費單位">Request Unit：把多維資源用量折算成單一計費單位</h3>
<p>serverless 的計費核心是 Request Unit（RU）— 一個把 <em>CPU、IO、network、storage 存取</em> 等多維資源用量折算成的抽象單位。每個 SQL 請求依其實際消耗的資源換算成若干 RU，帳單按 RU 總量計。這跟 dedicated「按 provision 的節點數 × 時間」計費是兩種不同的成本心智模型。</p>
<p>RU 模型的好處是 <em>用多少付多少</em> — 閒置時段不付運算費。風險是 RU 跟「人類直覺的請求數」不是線性對應：一個全表掃描的 query 可能吃掉相當於上千個點查的 RU。estimate workload 成本時，要以 <em>資源消耗</em> 為單位思考，不是以「請求數」。</p>
<blockquote>
<p><strong>Scope warning</strong>：RU 的具體換算係數、serverless 免費額度、scale-to-zero 的觸發閒置時間、冷啟動延遲量級、serverless 的 region / 一致性 / 規模上限，都屬 Cockroach Cloud 的計費與規格、且隨方案版本演進，三個 anchor case（DoorDash / Netflix / Hard Rock 全為 self-managed）都未揭露 serverless 計費數字。本文只給結構性判讀（RU = 多維資源折算、scale-to-zero 帶來冷啟動），具體數值與當前方案邊界需 cross-verify <a href="https://www.cockroachlabs.com/docs/cockroachcloud/plan-your-cluster">Cockroach Cloud Pricing 文件</a> 與官方計費頁。</p></blockquote>
<h3 id="自動-scale-與-scale-to-zero">自動 scale 與 scale-to-zero</h3>
<p>serverless 隨 workload 自動伸縮資源，無需團隊 provision。閒置時可 scale 到接近零，這正是「閒置不付運算費」的來源。對 <em>突發 + 長閒置</em> 的 workload（開發 / 測試環境、低流量 side project、流量極不均的早期產品），這個模型把成本壓到只反映實際活躍時段。</p>
<p>scale-to-zero 的代價是冷啟動 — 從近零狀態接到請求時，要先把資源拉起來，第一個請求的延遲高於 warm 狀態。對開發環境這通常可接受；對「閒置後第一個用戶請求就要快」的面向用戶 production 路徑，冷啟動是要先評估的取捨。</p>
<h3 id="serverless-vs-dedicated-的責任與成本對照">serverless vs dedicated 的責任與成本對照</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>serverless</th>
          <th>dedicated</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>容量決策</td>
          <td>自動 scale、無需 sizing</td>
          <td>團隊決定 cluster 規模</td>
      </tr>
      <tr>
          <td>計費單位</td>
          <td>RU（按實際資源用量）</td>
          <td>按 provision 的節點 × 時間</td>
      </tr>
      <tr>
          <td>閒置成本</td>
          <td>接近零（scale-to-zero）</td>
          <td>仍付 provisioned 容量費</td>
      </tr>
      <tr>
          <td>冷啟動</td>
          <td>閒置後第一請求有冷啟動延遲</td>
          <td>無（容量常駐）</td>
      </tr>
      <tr>
          <td>成本可預測性</td>
          <td>隨用量浮動、突發時可能暴衝</td>
          <td>固定、可預算</td>
      </tr>
      <tr>
          <td>性能可預測性</td>
          <td>共享底層、受鄰居影響</td>
          <td>專屬資源、更可預測</td>
      </tr>
  </tbody>
</table>
<p>每一行都要回到 workload 形狀判讀。</p>
<p>容量決策這一行是兩種模型的根本差異：serverless 把「要開幾個節點」這個決策從團隊手上拿走，對沒有容量規劃經驗或流量極不可預測的場景能降低團隊的容量規劃負擔；但對流量已知、需要性能可預測的 production，dedicated 的「自己定容量」反而是想要的控制權。</p>
<p>成本可預測性這一行是 serverless 的主要風險面。RU 隨用量浮動意味著 <em>一次失控的查詢模式、一波爬蟲、一個沒加 LIMIT 的全表掃描</em> 都會把帳單推高，而 dedicated 的成本上限就是 provisioned 容量。流量可預測的 production，dedicated 的可預算性往往比 serverless 的「用多少付多少」更重要。</p>
<h2 id="操作流程選型判讀配置用量驗證">操作流程：選型判讀、配置、用量驗證</h2>
<h3 id="第一步用流量形狀做-serverless--dedicated-初判">第一步：用流量形狀做 serverless / dedicated 初判</h3>
<p>選型的判讀軸是 workload 的 <em>流量形狀</em>，不是規模大小。</p>
<ul>
<li>流量突發 + 長閒置（dev / test、低流量產品、不可預測早期 workload）→ serverless 的 scale-to-zero 與按用量計費直接受益。</li>
<li>流量穩定 + 可預測 + 需要性能可預測 → dedicated 的固定容量與可預算成本更合適。</li>
<li>流量大 + 有專屬 Platform Team + 需要跨雲 / on-prem / 特定部署位置（如 Hard Rock 的合規 Outposts）→ 兩種 managed 都不對，走 self-managed（見 vendor overview 的容量規劃段）。</li>
</ul>
<p>判讀訊號：把過去一段時間的 QPS 畫成時間序列，看「活躍時段佔比」與「峰谷比」。活躍佔比低、峰谷比高 → serverless;活躍佔比高、波動平緩 → dedicated。</p>
<h3 id="第二步serverless-建立-cluster-並設成本上限">第二步：serverless 建立 cluster 並設成本上限</h3>
<p>serverless 的成本風險來自用量浮動，所以建立後第一件事是設 <em>消費上限</em>，把「用量暴衝 = 帳單暴衝」的尾部風險封住。</p>
<p>驗證點：cluster 建立後，確認消費上限已設、且設了接近上限的告警閾值（例如達上限 80% 告警）。沒設上限的 serverless cluster 等於把成本曝險完全交給 workload 行為。</p>
<h3 id="第三步驗證-ru-消耗與預期一致">第三步：驗證 RU 消耗與預期一致</h3>
<p>上線後監控 RU 消耗速率，對照第一步的流量形狀預估。</p>
<p>驗證點：RU 消耗速率若遠高於預估，通常是某類 query 的資源消耗被低估（全表掃描、缺索引、N+1 查詢）。這時要回到 query 層優化，而非直接加預算 — serverless 的計費把「低效 query」直接翻譯成「高帳單」，是一個比 dedicated 更直接的成本訊號。</p>
<h3 id="第四步評估冷啟動對-production-路徑的影響">第四步：評估冷啟動對 production 路徑的影響</h3>
<p>若 serverless cluster 服務面向用戶的 production 路徑，驗證閒置後第一個請求的延遲是否在 SLO 內。</p>
<p>驗證點：模擬閒置後的首請求延遲，對照面向用戶路徑的 latency SLO。超出 SLO 代表這條路徑不適合 scale-to-zero，要嘛保持一定 warm 流量、要嘛改 dedicated。</p>
<h2 id="失敗模式成本失控與選型誤判">失敗模式：成本失控與選型誤判</h2>
<h3 id="ru-用量暴衝帳單失控高代價情境的回退敘事">RU 用量暴衝、帳單失控（高代價情境的回退敘事）</h3>
<p>serverless 最常見的事故是 <em>帳單暴衝</em> — 一波非預期流量、一個低效查詢上線、一次爬蟲，把 RU 消耗推到遠超預算。跟 dedicated「成本上限 = provisioned 容量」不同，serverless 的成本上限要靠人為設定，沒設就沒有天花板。</p>
<p>這個情境的回退代價特殊之處在於 <em>成本已經發生</em>：rebalance 可以暫停、locality 可以改回，但已計的 RU 帳單不會退回。所以 serverless 成本失控的「回退」重點在 <em>事前封頂</em> 與 <em>事中熔斷</em>，而非事後補救。</p>
<p>回退與防護要素：</p>
<ul>
<li>事前一定設消費上限與分級告警（接近上限前就要收到訊號），把尾部風險封在可承受範圍。</li>
<li>事中發現 RU 暴衝，先定位來源 — 是流量真的漲（業務事件），還是某個 query 模式失控（缺索引、全表掃描、無 LIMIT）。前者考慮是否該轉 dedicated，後者回 query 層修。</li>
<li>設「RU 消耗速率超過閾值就告警 + 自動限流」的 tripwire，避免單一失控 query 在無人值守時段燒完整月預算。</li>
<li>若 workload 已穩定成長到「serverless 浮動成本 &gt; dedicated 固定成本」的交叉點，規劃轉 dedicated。</li>
</ul>
<h3 id="serverless--dedicated-遷移的代價">serverless → dedicated 遷移的代價</h3>
<p>當 workload 從「突發長尾」成長為「穩定高量」，serverless 的按用量成本會超過 dedicated 的固定成本，此時要遷移。這個遷移不是改個開關 — serverless 與 dedicated 是不同的 cluster 形態，遷移意味著資料搬遷與 cutover，要走 backup / restore 或資料複製流程，並承擔 cutover 窗口。</p>
<p>回退敘事：把 serverless → dedicated 當成一次小型 migration 規劃 — 估資料量與遷移窗口、雙寫或 backup/restore 路徑、cutover 條件與回退條件，而非「線上無痛切換」。提早在用量逼近成本交叉點時規劃，避免在帳單已經失控時倉促遷移。</p>
<p>Anti-recommendation：不要因為「serverless 聽起來更現代」就把已知穩定、可預測、高流量的 production workload 開在 serverless。這類 workload 的可預算性與性能可預測性，dedicated 給得更直接，serverless 反而引入成本浮動與冷啟動兩個非必要風險。</p>
<h3 id="把賽季型--可預測擴縮誤當-serverless-場景">把賽季型 / 可預測擴縮誤當 serverless 場景</h3>
<p>可預測的擴縮（如 Hard Rock 的 NFL / NBA 賽季 100 ↔ 33 node 年度循環）不是 serverless 的適配範圍。serverless 擅長 <em>不可預測</em> 的突發，而可預測的擴縮可以用 dedicated 的計畫內 scale 直接規劃容量、保留性能可預測性。把可預測擴縮交給 serverless，是用「成本浮動 + 冷啟動」換一個本來就能用排程解決的問題。</p>
<p>修法：可預測的容量循環，用 dedicated + 排程 scale；只有真正不可預測的突發長尾才用 serverless。</p>
<h3 id="冷啟動拖垮面向用戶路徑">冷啟動拖垮面向用戶路徑</h3>
<p>scale-to-zero 的 serverless cluster 服務面向用戶 production，閒置後首請求冷啟動延遲超出 SLO，用戶感受到第一次訪問特別慢。</p>
<p>修法：面向用戶且對首請求延遲敏感的路徑，要嘛維持低頻 warm 流量避免完全 scale-to-zero，要嘛改 dedicated；scale-to-zero 留給容忍冷啟動的 dev / test / 後台 batch 路徑。</p>
<h2 id="容量與觀測">容量與觀測</h2>
<h3 id="必看-metric">必看 metric</h3>
<ul>
<li><code>RU 消耗速率</code>：serverless 成本的直接訊號，速率異常上升要立刻定位 query 來源。</li>
<li><code>當期累計消費 vs 上限</code>：成本封頂的剩餘空間，逼近上限要告警。</li>
<li><code>冷啟動 / 首請求延遲</code>：scale-to-zero 對面向用戶路徑的影響。</li>
<li><code>query 資源消耗分佈</code>：哪些 query 吃掉最多 RU，是 serverless 成本優化的入口。</li>
</ul>
<h3 id="容量與成本判讀">容量與成本判讀</h3>
<ul>
<li>serverless 月成本 ≈ Σ(各 query RU × 頻率)，所以成本優化等於 query 效率優化 — 缺索引、全表掃描在 serverless 直接體現為帳單。</li>
<li>serverless / dedicated 成本交叉點 ≈ 「serverless 浮動成本」與「dedicated 固定容量成本」相等的用量水準，逼近交叉點是規劃遷移的訊號。</li>
<li>dedicated 的容量規劃回到節點數 × replica × latency budget（見 vendor overview 容量規劃段）。</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：RU 換算係數、免費額度、serverless 的規模 / region / 一致性上限、serverless ↔ dedicated 成本交叉點的具體用量水準，均為 Cockroach Cloud 計費與規格、隨方案版本變動，非 case 揭露數字，成本建模前以 <a href="https://www.cockroachlabs.com/docs/cockroachcloud/">Cockroach Cloud 文件</a> cross-verify。</p></blockquote>
<h3 id="回路徑">回路徑</h3>
<ul>
<li><a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 流量形狀 → 計費模型對應。</li>
<li><a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> managed vs self-managed 的人力 + 資源成本權衡。</li>
</ul>
<h2 id="邊界與整合">邊界與整合</h2>
<h3 id="sibling-deep-articles">Sibling deep articles</h3>
<ul>
<li><a href="../survival-goals/">survival goals</a>：managed 形態下 survival goal 仍是團隊決策 — serverless / dedicated 都要對齊業務 RTO / RPO，存活機制以該文為 SSoT。</li>
<li><a href="../multi-region-table-config/">multi-region table config</a>：serverless 與 dedicated 對 multi-region table locality 的支援邊界不同，跨 region 強一致需求要先確認所選 managed 形態是否覆蓋。</li>
<li><a href="../aurora-dsql-spanner-decision-tree/">aurora-dsql-spanner-decision-tree</a>：Aurora DSQL 本身是 serverless distributed SQL，三家 managed distributed SQL 的選型對比以該文為 SSoT，本文不重展。</li>
</ul>
<h3 id="跟-aurora-dsql--spanner-serverless-對照">跟 Aurora DSQL / Spanner serverless 對照</h3>
<p>Aurora DSQL（AWS）以 serverless 為核心形態、AWS-only；Spanner 提供 managed 但計費與 scale 模型不同。三家在 serverless / managed 維度的完整對比是 <a href="../aurora-dsql-spanner-decision-tree/">aurora-dsql-spanner-decision-tree</a> 的 SSoT，本文只處理 Cockroach Cloud 自身的 serverless / dedicated 取捨。</p>
<h3 id="跟-self-managed-對照">跟 self-managed 對照</h3>
<p>self-managed（如 Netflix 380+ cluster、Hard Rock 合規 Outposts）給最大控制權（跨雲 / on-prem / 特定部署位置），代價是專屬 Platform Team 的運維責任。判讀軸：沒有 Platform Team → managed（serverless / dedicated）；有 Platform Team + 需要特定部署位置或跨雲 → self-managed。</p>
<h3 id="1x-章節互引">1.x 章節互引</h3>
<ul>
<li><a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> 上游選型。</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/migrate-to-cockroachdb/" data-link-title="PostgreSQL → CockroachDB：三維皆 High 的多重歸類 migration" data-link-desc="PostgreSQL → CockroachDB 是 Schema / Operational / Paradigm 三維皆 High 的 multi-axis migration、實證 [#127](/report/content-structure-by-max-diff-dimension/) 的「多重歸類跟 tie-breaking」規則；主結構走 Type E paradigm shift、Schema 差 &#43; Operational redesign 抽出獨立段；涵蓋 transaction model 重設計、SQL dialect gap、5 個 production 踩雷">PostgreSQL → CockroachDB migration</a> — 從 PostgreSQL 遷入後再選 managed 形態。</li>
</ul>
<h3 id="何時不用本文">何時不用本文</h3>
<ul>
<li>已決定 self-managed（有 Platform Team 或需要 on-prem / 合規 Outposts）→ 看 vendor overview 容量規劃段與 self-host 運維，本文的 serverless / dedicated 取捨不適用。</li>
<li>single-region 小 workload 且 PostgreSQL 已夠用 → 先確認是否真需要 distributed SQL，見 vendor overview 不適用場景。</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li><a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor overview</a></li>
<li><a href="/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/" data-link-title="9.C40 Netflix：380&#43; CockroachDB cluster 的 multi-active 拓樸艦隊" data-link-desc="Netflix 把 Cassandra 不夠用的 transactional workload 移到 CockroachDB、380&#43; cluster / 60&#43; 跨 region、含 Open Connect、studio cloud drive、gaming control plane">9.C40 Netflix</a>（self-managed 需 Platform Team 的反向 = managed 入口）</li>
<li><a href="/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/" data-link-title="9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 &#43; 跨州單一邏輯 DB" data-link-desc="Hard Rock Digital 用 CockroachDB 跨 AWS Outposts &#43; US-East-1、Wire Act 強制資料留州、單一邏輯 DB 解多州 sportsbook、100 node 32 vCPU 撐 Super Bowl">9.C41 Hard Rock Digital</a>（可預測賽季擴縮 vs serverless 突發適配範圍的對照）</li>
<li><a href="/blog/backend/knowledge-cards/distributed-sql/" data-link-title="Distributed SQL" data-link-desc="把 SQL 與交易語意延伸到多節點與多區域的資料庫形態">distributed SQL 卡</a></li>
<li>官方：<a href="https://www.cockroachlabs.com/docs/cockroachcloud/">Cockroach Cloud Documentation</a> / <a href="https://www.cockroachlabs.com/docs/cockroachcloud/plan-your-cluster">Plan Your Cluster</a></li>
</ul>
]]></content:encoded></item></channel></rss>