跳到內容

儲存選型:KV / D1 / R2 / DO / Hyperdrive 決策樹

查證日期

前五篇各自深入一個產品。這一篇把它們放在一起,回答唯一真正重要的問題:面對一個新需求,五分鐘內選對儲存並說得出理由。

這章沒有新實驗,是一份參考。建議看完之後把決策圖和成本表存下來 —— 本系列後面每次遇到「這個要存哪」的時候都會回頭用它。

一句話總結整個 Cloudflare 儲存生態的設計:

每個產品都在一個不同的維度上收費,而那個維度就是它的物理限制。

KV 按操作次數收費,因為它是全球複製的;D1 按掃描列數收費,因為它是單執行緒的;R2 按操作類別收費,因為列表和讀取的成本結構不同;DO 按 GB-秒收費,因為它是有生命週期的行程。看懂計費維度,就看懂了這個產品該用在哪。


本質一句話
Workers Cache(第 6 篇)Worker 前面的快取命中就不執行你的程式碼
KV(第 8 篇)全球複製的唯讀快取讀多寫少、可接受最終一致
D1(第 9 篇)邊緣上的 SQLite唯一有關聯查詢和唯一約束的選項
R2(第 12 篇)物件儲存存位元組,不存索引
Durable Objects(第 14 篇)全球唯一、單執行緒的 actor解決協調問題,不是儲存問題
Hyperdrive(第 28 篇)既有資料庫的連線加速層你已經有 Postgres/MySQL 時的橋

最容易被誤解的是 DO。它看起來像「有狀態的儲存」,但它真正解決的是協調:原子遞增、單一真相、即時廣播、per-entity 排程。如果你的需求不需要「同一時間只有一個執行緒在改這份狀態」,那你大概不需要 DO。


這是選型的第一道篩子。

一致性read-your-own-write跨 colo
Workers Cache無保證(快取)分層,非全域一致
KV最終(可能 60 秒以上)不保證是(最終)
D1(無複本)單一 primary
D1(有複本)最終,除非用 withSession()靠 bookmark六個區域
R2put 強一致
DO序列化(單執行緒)單一實例,全球唯一
Hyperdrive由你的來源資料庫決定是(但快取不會因寫入失效

三個值得單獨記住的:

  • KV 的 read-your-own-write 不保證。 登出、撤銷、權限變更這類「必須立即生效」的語意,KV 給不了。
  • D1 開了複本但沒用 withSession(),一致性沒變(因為全部還是打 primary),但你也沒得到延遲好處。
  • Hyperdrive 的 query cache 不會被寫入失效(第 28 篇)。read-after-write 的路徑要用第二組關閉快取的設定。

這是本篇最有價值的一張表。注意每一行的「計費單位」都不一樣 —— 這就是為什麼不能只比「每 GB 多少錢」。

計費單位儲存
KV操作次數$0.50 / 百萬$5.00 / 百萬$0.50 / GB-月
D1掃描的列數$0.001 / 百萬列$1.00 / 百萬列$0.75 / GB-月
R2操作類別Class B $0.36 / 百萬Class A $4.50 / 百萬$0.015 / GB-月
DOGB-秒 + 請求$0.15/百萬請求 + $12.50/百萬 GB-s同左$0.20 / GB-月
Workers Cache不另計費

(Paid 方案,含免費額度之後的費率,2026-07 查證。)

① KV 的寫入比讀取貴 10 倍。 任何寫入頻繁的東西都不該放 KV —— 而且它還有每個 key 每秒 1 次的硬上限。

② D1 的成本由索引決定,不由資料量決定。 第 9 篇實測:同一個查詢,有索引 rows_read: 1,沒索引 rows_read: 2000同一份資料、同一個查詢、2000 倍價差。

③ R2 的儲存最便宜(比 KV 便宜 33 倍),但列表最貴。 ListObjects 是 Class A,是 GetObject 的 12.5 倍。所以大檔案放 R2,清單放 D1

④ DO 的成本是時間,不是次數。 閒置的 DO 如果用了 WebSocket hibernation(第 16 篇)就幾乎不計費;沒用的話會持續累積 GB-秒。


流程圖

渲染版(可列印):assets/13-storage-decision-tree.svg

三個路徑上的提醒:

  • 「熱路徑 + 可容忍舊資料」永遠先想快取,再想 KV。 第 8 篇的成本試算:加了 Workers Cache 之後 KV 讀取從 1,000 萬次降到 100 萬次,成本從 $5.00 變 $0.50。
  • 走到「單一 DO 是全域瓶頸」代表切分方式有問題。 一個 DO 大約每秒 1,000 個請求就是上限,而且沒辦法橫向擴展。
  • 「簡單也是一種價值」不是玩笑。 資料量不大、流量不高的時候,全部放 D1 是完全合理的選擇。過早的儲存分層是真實的成本。

五、用 LinkForge 的六個真實需求走一遍

Section titled “五、用 LinkForge 的六個真實需求走一遍”
#需求選擇決定性理由
1slug → URL 的 redirect 查詢Workers Cache → KV極端讀多寫少、可容忍 60 秒、每秒數千次。D1 單執行緒撐不住,DO 太貴
2連結的真相來源(唯一約束、租戶關聯)D1只有 D1 有 UNIQUE INDEXbatch() 的原子性
3即時點擊計數Durable ObjectKV 每個 key 每秒 1 寫直接判死;需要原子遞增
4匯出的 CSV / OG 圖 / QR codeR2 + D1 清單位元組放最便宜的地方,list() 是 Class A 所以清單放 D1
5使用者 sessionDO(第 27 篇)「登出所有裝置」需要立即失效,KV 的最終一致給不了
6每租戶速率限制ratelimits binding(第 4 篇)內建、本機可跑、不需要額外儲存;要精確全域計數才升級到 DO

注意第 4 項的拆法:同一個需求同時用了兩個產品。這是常態而不是例外 —— R2 存位元組、D1 存「有哪些位元組」。第 12 篇的成本試算顯示,如果清單改用 list(),光那一項就比所有其他操作加起來還貴。


反模式為什麼錯改用
KV 當計數器沒有原子遞增;每個 key 每秒 1 寫DO(第 14 篇)
KV 當 session store(需要撤銷)read-your-own-write 不保證DO 或 D1
KV 當 queue沒有排序、沒有 ackQueues(第 19 篇)
D1 在熱路徑單執行緒,吞吐量 = 1 / 查詢時間Cache → KV
D1 沒建索引rows_read 就是帳單,實測 2000 倍差加索引 + EXPLAIN QUERY PLAN
db.transaction() on D1型別會過、runtime 會死(第 10 篇)batch() 或 DO
R2 的 list() 當查詢Class A,12.5 倍價清單存 D1
R2 熱點寫入每個 key 每秒 1 次,超過 429DO
單一 DO 服務全部流量每秒約 1,000 請求上限,無法橫向擴展按實體切分
DO 不用 hibernation 的 WebSocket持續累積 GB-秒ctx.acceptWebSocket()(第 16 篇)
Hyperdrive 讀寫同一組設定快取不會因寫入失效第二組 --caching-disabled
r2.dev 服務 production限速,且繞過 WAF / Access自訂網域

選型不是一次性決定。這是幾條實際的遷移路徑:

從 → 到難度做法
D1 → 加一層 KV 快取加投影寫入 + 讀取路徑改順序。D1 仍是真相來源
KV → D1KV 通常沒有完整資料(value 是投影),要從別處重建
D1 計數器 → DO需要一次資料遷移 + 雙寫過渡期
單一 DO → 多個 DO切分鍵一旦選錯,重新切分等於重寫
R2 全掃 → D1 索引一次性 list() 灌進 D1,之後只寫 D1

最貴的錯誤是 DO 的切分鍵。 其他都可以增量修,切分鍵錯了要重來。所以第 14 篇會花很多篇幅在「怎麼選 DO 的 id」。

一個實用的原則:先用 D1 做對,再用快取做快。 D1 有最完整的語意(約束、關聯、原子性),先確保正確性,效能問題出現時再往前加 KV 或 Cache —— 這個方向的遷移成本最低。反過來(先用 KV 圖快,後來發現需要約束)就痛苦得多。


如果只能記一件事,記這個:

需要「同一時刻只有一個人在改」 → Durable Object
需要「不能重複 / 關聯查詢」 → D1
需要「存很多位元組」 → R2(清單放 D1)
需要「全球讀取快、寫得少」 → KV(前面加 Workers Cache)
需要「整個回應可快取」 → Workers Cache(最便宜)
已經有 Postgres / MySQL → Hyperdrive

以及三個成本直覺:

  • KV 寫比讀貴 10 倍
  • D1 的錢花在索引上,不在資料量上
  • R2 的 list()get() 貴 12.5 倍

六篇下來,LinkForge 的資料層定案了:

Workers Cache ← 第 6 篇:命中就不執行 Worker
↓ miss
redirect ────────► KV ← 第 8 篇:slug → URL + metadata
↑ 投影
API / dashboard ──► D1 (Drizzle) ← 第 9–11 篇:真相來源
↑ rollup
點擊事件 ────────► DO ← 第 14–17 篇(Part 3)
匯出 / OG 圖 ─────► R2 + D1 清單 ← 第 12 篇

每一個箭頭都有理由,而且每個理由都可以用一個計費維度或一致性保證來解釋。

Part 2 交付物總結packages/db 完整落地、apps/redirector 接上 KV、apps/api 接上 D1、R2 的 key 規則與下載路由、以及三份護欄(rows_read 記錄、query plan 測試、bookmark 格式驗證)。



下一篇14. Durable Objects:邊緣上的 actor —— Part 3 開始。DO 解決的是協調問題而不是儲存問題,而選錯切分鍵是本系列最貴的錯誤。