我該用哪種儲存?KV / D1 / R2 / Durable Objects / Hyperdrive 選型
五種儲存、一個 Worker。問自己幾個問題,答案自然浮現。
我們在選什麼?
Cloudflare 提供五種差很多的資料儲存方式,它們全都掛在你的 Worker 後面。選錯會讓一切變難;選對則讓程式碼又短又快。這份指南把「該用哪個」變成幾個是/否的問題。
先把所有角色擺上同一個舞台:Worker 是大腦,每種儲存各演一個角色。KV 是快取、D1 是 SQL 資料表、R2 是檔案櫃、Durable Objects 是「唯一真相來源」、Hyperdrive 是通往你既有資料庫的快速通道。
把它想成…
一間廚房。KV 是調味料架(隨手拿、偶爾補貨)、D1 是食譜本(有結構、可查找)、R2 是大型冷凍庫(又大又笨重的東西)、Durable Objects 是主廚(手上握著唯一一份正式點單)、Hyperdrive 則是通往你「現有供應商」的送貨捷徑。
決策樹
從最上面開始,照著答案往下走,每條分支都會落在一個產品上。之後你當然可以混搭多種儲存,但對「單一一筆資料」來說,這棵樹會給你一個明確的預設答案。
順序有意義
先問「是不是大檔案」(R2 最好認),再問「需不需要關聯式」(D1 或 Hyperdrive),接著問「要不要強一致性」(Durable Objects)。如果都不是,那通常就是一筆「讀多寫少」的小資料,答案幾乎都是 KV。
五個選項一次看懂
每個儲存一張卡片,附上「什麼時候該用它」的一句話原則。
Workers KV — 何時用…
小資料、讀遠多於寫,而且約 60 秒的全球同步可以接受:快取、功能開關、設定、登入工作階段。
D1 — 何時用…
需要關聯式資料表與 SQL(JOIN、WHERE、ORDER BY),而且想要一個 Cloudflare 原生、不用自己養伺服器的資料庫。
R2 — 何時用…
要存大型檔案——圖片、影片、PDF、備份——而且想要零「egress(對外下載流量)」費用。
Durable Objects — 何時用…
某一份狀態必須強一致且需要協調:計數器、聊天室、遊戲狀態、鎖、即時協作。
Hyperdrive — 何時用…
你已經有 Postgres / MySQL 資料庫,只想讓它從 Workers 連起來變快——不用搬遷、不用改寫。
每個 binding 在程式碼裡長怎樣
「binding(綁定)」是你在 wrangler.jsonc 宣告的一個名字,它會以 env.某某 的形式出現在 Worker 裡。下面是每種儲存最小的真實片段——注意它們的存取方式差很多。
KV — 讀取 / 寫入一個值
用 key 查找;值是字串(或串流)。可選的 TTL 會讓 key 自動過期。
// wrangler.jsonc -> { "kv_namespaces": [{ "binding": "KV", "id": "..." }] } const cached = await env.KV.get("user:42"); await env.KV.put("user:42", JSON.stringify(user), { expirationTtl: 3600 });D1 — 預備式 SQL 查詢
用 ? 佔位符寫 SQL,用 bind() 填值,再用 .all() 取回資料列。
// wrangler.jsonc -> { "d1_databases": [{ "binding": "DB", "database_name": "app" }] } const { results } = await env.DB .prepare("SELECT * FROM orders WHERE city = ?") .bind("Taipei") .all();R2 — 存入 / 取出一個物件
用 put() 以 key 存檔,再用 get() 取回一個物件,它的 .body 是串流。
// wrangler.jsonc -> { "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "files" }] } await env.BUCKET.put("invoice.pdf", request.body); const object = await env.BUCKET.get("invoice.pdf"); return new Response(object.body);Durable Objects — 呼叫 stub
getByName() 會回傳「那個名字唯一的一個實例」;直接用 RPC 呼叫它的方法。所有呼叫者都打到同一個物件。
// wrangler.jsonc -> durable_objects: { "name": "ROOM", "class_name": "Room" } const stub = env.ROOM.getByName("room-42"); const count = await stub.increment(); // strongly consistentHyperdrive — 連線字串
讀取 env.HYPERDRIVE.connectionString,用一般的 pg 客戶端連線;連線池與快取都自動幫你處理。
// wrangler.jsonc -> { "hyperdrive": [{ "binding": "HYPERDRIVE", "id": "..." }] } import { Client } from "pg"; const client = new Client({ connectionString: env.HYPERDRIVE.connectionString }); await client.connect(); const { rows } = await client.query("SELECT * FROM products LIMIT 10");
選型背後的關鍵字
大多數的選擇其實是被三個概念決定的:一致性、關聯式 vs 鍵值、以及物件儲存。搞懂它們,那棵決策樹就一目了然。
最終一致性
寫入後,其他地區可能要約 60 秒才看得到新值——它們會「最終」追上。KV 就是這樣:拿來做快取很棒,拿來存銀行餘額就錯了。
強一致性
每個讀取者永遠、立刻看到最新的寫入。Durable Object 能做到,因為「剛好只有一個實例」擁有這份資料、並依序處理請求。
關聯式 (SQL)
資料放在有欄位與關聯的「資料表」裡,用 SQL 查詢——JOIN 串接資料表、WHERE 過濾、ORDER BY 排序。D1(以及透過 Hyperdrive 連的自家資料庫)都是關聯式。
鍵值
沒有資料表、沒有 SQL——只有一個名字(key)指向一個值。又快又簡單,但你只能用「精確的 key」查找。KV 就是純粹的鍵值儲存。
物件儲存
專為大型、內容不透明的檔案(「物件」)打造,例如圖片與影片,用一個 key 來定址。R2 就是物件儲存,而且下載這些檔案不收 egress 流量費。
協調
當很多使用者同時動到「同一個共享的東西」——同一個計數器、同一個聊天室——必須有人把它們排隊,更新才不會打架。那個「唯一擁有者」正是 Durable Objects 提供的。
速查表、混搭與陷阱
什麼時候用哪個…
- 用 KV:小資料讀遠多於寫,且約 60 秒的全球同步可接受(快取、設定、工作階段)。
- 用 D1:需要關聯式資料表與 SQL 查詢(JOIN/WHERE/ORDER BY),且想要 Cloudflare 原生資料庫。
- 用 R2:要存大型檔案——圖片、影片、PDF、備份——而且想要零 egress 流量費。
- 用 Durable Objects:某一份共享狀態必須強一致且需要協調(計數器、聊天室、鎖)。
- 用 Hyperdrive:你已在別處跑 Postgres/MySQL,只想讓它從 Workers 連起來變快——不用搬遷。
其實可以混搭
真實的應用通常會混用:把上傳的檔案放 R2、它的中繼資料放 D1、把熱門查找快取在 KV,再用一個 Durable Object 守住「絕對不能重複計數」的那個計數器。
常見選錯
別把大型檔案塞進 KV(單筆上限 25 MiB,本來就不是給二進位物件用的),也別用 KV 存「全球每處都得即時最新」的資料。別只為了放靜態資料就動用 Durable Objects——那種用一張資料表就好。如果你根本沒有既有資料庫,就別選 Hyperdrive,直接從 D1 開始。
- KV:讀多寫少、最終一致、只有鍵值、單筆最多 25 MiB。
- D1:以 SQLite 為基礎的關聯式 SQL、有讀取副本,是應用資料的好預設。
- R2:相容 S3 的物件儲存、無 egress 流量費,最適合媒體與備份。
- Durable Objects:一個名字對應一個實例、強一致、內建自己的 SQLite。
- Hyperdrive:在你既有 Postgres/MySQL 前面加上連線池與快取——它不是一個新資料庫。