降低多租戶 PaaS 與雲端託管的 Cloudflare Workers KV 讀取

我們從每個請求都讀取 KV,改為重複利用可快取的路由判斷。本文記錄 KamuiDash 如何最佳化多租戶 PaaS 與雲端託管平台的 Cloudflare Worker 路由。

多租戶 PaaS 或網站託管平台會讓大量 Host 與請求集中到同一個入口。
僅僅讓 Worker 保持輕量還不夠;KV 讀取、靜態檔案取得、快取傳播與未知 Host,都必須視為同一條請求路徑來設計。

在 KamuiDash 的公開入口,Cloudflare Worker 會從 Host 解析租戶應用程式,再依路由將請求送往動態應用程式或靜態內容交付路徑。完整架構可參考以 Cloudflare 作為入口的多租戶 PaaS 設計

本文聚焦於如何降低共用入口產生的 KV 讀取。目標是不再為每個請求重問 KV 相同問題,而是在同一個 Cloudflare 資料中心內短暫共用已解析的路由結果。

最佳化後的請求路徑REQUEST PATH
01請求Host + Path
02靜態內容快取HIT 時略過後段讀取
03Route Cache依 Host 短暫共用
04路由 KV只有 MISS 才讀取
STATIC靜態內容交付HTML / CSS / Assets
DYNAMICTunnel + App不共用動態內容
先做成本最低的判斷,再依序處理靜態內容、已解析路由、KV 與交付目標。

最初的問題:結果正確,但每次都讀取

最初的路由模型刻意保持簡單。自訂網域先查詢其內部應用程式,再查詢應用程式的交付資訊;平台子網域也從 KV 解析動態或靜態路由資料。

這個模型容易理解,在低流量時也足夠。然而當整個 PaaS 的流量成長,即使應用程式完全沒有變更,同一份路由資訊仍會被重複讀取。

最佳化前的讀取PER REQUEST
PLATFORM HOSTapp.platform.example
APP KV
每個請求至少一次讀取
CUSTOM DOMAINcustomer.example
DOMAIN KVAPP KV
兩階段解析
即使同一個 Host 連續收到請求,每次 Worker 執行仍會重做相同的 KV 操作。

1. 平台擁有的 Host 不查詢網域對應 KV

第一步是停止向 KV 詢問 Host 本身已經能確定的資訊。平台 apex 與萬用字元子網域本來就能作為內部應用程式名稱;只有真正的自訂網域需要從公開 Host 轉換成內部 Host。

Host 解析決策樹DOMAIN LOOKUP
收到的 Host 屬於誰?只使用正規化後的 hostname 判斷
APEXplatform.example送往 landing 應用程式DOMAIN KV: 0
SUBDOMAIN*.platform.example直接作為內部 HostDOMAIN KV: 0
CUSTOMcustomer.example解析內部 HostDOMAIN KV: 1
Host 本身已能確定所有權,因此平台網域可以完全略過第一個 KV 讀取。

2. 整合動態與靜態路由記錄

動態應用程式與靜態網站原本使用不同路由表。我們將它們整合成一個路由 KV,讓一次應用程式查詢就能判斷交付類型並取得所需資料。

整合路由記錄ONE ROUTING RECORD
BEFORE
動態路由 KVapp → 動態交付資料
靜態路由 KVapp → 靜態交付資料
AFTER
整合路由 KV交付類型、狀態與必要資料
放在同一筆記錄
控制平面與 Worker 同時切換到新結構,不保留長期的新舊讀取 fallback。

遷移時,我們先將記錄複製到新 KV,確認數量與重複項目,再切換 Worker。我們刻意移除舊讀取 fallback,避免長期存在兩個事實來源而增加故障排查難度。

3. 在 KV 之前檢查靜態內容快取

如果同一個靜態 URL 被反覆請求,就能在解析 Host 前直接回傳快取內容。因此,我們把以公開 URL 為鍵的靜態內容快取移到所有路由查詢之前。

調整處理順序EARLY RETURN
BEFORE
  1. 解析 Host
  2. 讀取路由 KV
  3. 判斷靜態路由
  4. 檢查內容快取
  5. 取得靜態檔案
AFTER
  1. 檢查內容快取
  2. 解析 Host
  3. 讀取路由 KV
  4. 取得靜態檔案
靜態快取 HIT 不會到達 KV 或後段靜態交付處理。Worker 仍會執行,但會在入口附近結束。

共用快取可能包含其他回應,因此由此 Worker 寫入的項目會帶有私有標記。HIT 時先驗證標記,回傳前再移除;若後段也送來同名標頭,同樣會被移除,避免內部狀態暴露給使用者。

4. 以 Route Cache 共用已解析路由

除了靜態回應內容,我們也會把 Host 解析到已驗證路由記錄的結果存入 Cache API。動態應用程式的回應內容不會共用,只快取其路由。

快取鍵由正規化後的 Host 產生,並限制在平台管理的範圍。具體內部路徑與識別資訊不對外公開。

Route Cache MISS 與 HITSHORT CACHE WINDOW
FIRST REQUEST
WorkerRoute Cache MISSKV ReadCache store交付目標
NEXT REQUEST
WorkerRoute Cache HITKV Read 0交付目標
動態路由在 Route Cache HIT 後仍會送往應用程式,因為使用者專屬的回應內容不會共用。

只有狀態與交付資料通過驗證的記錄才會被儲存。缺少必要值時不會選擇隱含目的地,而是安全地停止路由;內部欄位名稱與識別資訊均省略。

5. 短暫快取已確認不存在的結果

如果只快取正常路由,對同一個不存在 Host 的重複查詢仍會到達 KV。Cloudflare 即使找不到鍵,也會計算該次 KV 操作。

因此,我們只將明確確認不存在的結果做短時間 negative cache。處理中狀態、格式錯誤記錄與上游失敗都不會被快取。

404 的 negative cacheNOT FOUND IS A RESULT
第一次請求未知 HostKV 確認不存在MISS
短時間儲存 404 結果只儲存不存在結果Route Cache
後續請求回傳相同 404不再到達 KV0 Reads
不存在也是可重複使用的結果。異常流量由獨立防護層控制,而不是只依賴快取行為。

代價是:如果某個 Host 在完成註冊前就被存取,建立後仍可能短暫回傳 404。我們讓 Route Cache 與 KV 邊緣快取共用同一個 freshness budget,避免明顯增加既有傳播時間。

TTL 越短不一定越好

TTL 不應只由成本決定,而要從新路由或新內容部署後可接受的顯示時間反推。我們不會獨立延長 KV 邊緣快取、Route Cache 與靜態內容快取,而是將它們重疊時的延遲視為同一個 freshness budget。具體數值不對外公開。

Cache API 與 Workers Caching 是不同機制

這次使用的是 Worker 程式碼內操作的 Cache API。Cloudflare 另有能在 Worker 執行前直接回傳回應的 Workers Caching,兩者彼此獨立。

兩種快取位置EXECUTION & BILLING
CACHE API
RequestWorker runsCache HIT
  • Worker 請求仍會計費
  • Worker CPU 會執行
  • HIT 時略過 KV 與後段讀取
WORKERS CACHING
RequestCache HITWorker skipped
  • Worker 請求仍會計費
  • Worker CPU 不執行
  • 略過 KV 與後段處理
Workers Caching HIT 仍計入 Worker 請求,但不會消耗 Worker CPU,也不會執行後段處理。

目前的 Worker 是靜態與動態路由的共用閘道。動態回應預設使用 private,不會存入 Workers Caching。由於路由查詢是眼前最直接的成本來源,我們優先導入 Route Cache。

在快取前後都驗證資料

快取會同時加速正確值與錯誤值,因此記錄在寫入前與讀取後都必須驗證。

先從開發環境逐步推出

路由器會影響所有租戶,因此我們依序在本機、開發環境與正式環境驗證。每次 Terraform plan 都必須只包含 Worker 的原地更新,不能混入 KV、靜態交付、DNS 或 Tunnel 的其他變更。

推出與驗證SAFE CHANGE
  1. 01本機測試正常、404、處理中、格式錯誤與快取失敗
  2. 02開發環境 plan只有預期的原地更新
  3. 03開發環境 URL動態、靜態與未知 Host
  4. 04正式環境 plan再次確認 Worker 單一變更
  5. 05正式環境 URL平台、自訂、靜態與 404 路由
完成推出前,我們也會比較已部署的 Worker 內容與審查過的本機原始碼。

本機測試涵蓋平台 Host、自訂網域、動態與靜態路由、正向與負向快取、處理中狀態、格式錯誤記錄與 KV miss;正式環境則以實際 URL 驗證相同的公開行為。

接下來的最佳化

路由 KV 的主要讀取削減已經完成,下一步應由實際量測決定。

總結

關鍵不是新增某一個快取,而是把成本最低且可靠的判斷放在最前面。

  1. 01
    Host 已知的資訊不要再詢問 KV平台擁有的 Host 可以直接辨識。
  2. 02
    整合用途相同的記錄一次讀取即可決定動態或靜態交付。
  3. 03
    先檢查內容快取靜態 HIT 可完全略過路由處理。
  4. 04
    短暫共用有效路由與 404不要為每個請求重做相同判斷。
  5. 05
    將 freshness 視為預算延長 TTL 前先設計可接受的傳播時間。

在多租戶入口,每一個小型讀取都會乘上所有租戶與請求。從 Workers、KV、Cache API 到交付路徑,設計每個請求能在哪裡停止,讓我們在不讓閘道變得難以維護的前提下降低讀取。

參考資料