Cloudflare 免费额度的真实边界
KV 每天 1000 次写、D1 每天 500 万行读——这些数字看起来很大,但踩过一次坑才知道,delete 也算写。一次事故换来的六条硬规则。
一次真实事故
某次部署后,我写了一个「清理过期缓存」的脚本,遍历 KV 里所有 cache:* 前缀的键然后逐个 delete。
本地测试通过,线上跑完也没报错。第二天早上,全站 API 开始返回 500——不是代码错,是 KV 写入配额被判定用尽。
原因很简单:KV 免费版每天 1000 次写入,而 delete 同样计入写入配额。那晚脚本删了 3000 多个键。
六条硬规则
1. delete 就是写
官方文档写得很清楚,但心理上很难接受。任何「批量清理」的念头,在动手前先问一句:这批操作会消耗多少次写?
2. 用版本号代替删除
缓存失效不要删键,改成把版本号 +1:
// ❌ 错误:N 个键 = N 次写
for (const key of allCacheKeys) await kv.delete(key);
// ✅ 正确:1 次写,旧条目靠 TTL 自然过期
await kv.put('cache:version', String(Date.now()), { expirationTtl: 86400 });
读取时把版本号拼进 key:cache:v{version}:{path}。旧版本的键没人再读,到期自动消失,全程 0 次删除。
3. TTL 是免费的
expirationTtl 不消耗额外配额,是这套系统里最便宜的资源。所有缓存都必须带 TTL,没有例外。
4. D1 的读很便宜,写很贵
免费版 D1 每天约 500 万行读、10 万行写。读基本用不完,但一次批量插入 1000 行就是 1000 次写。填充示例数据时要注意上限。
5. 静态优先
能塞进 R2(无请求数限制、只计存储和出流量)的就不要放 D1。图片、字体、主题包资源全部走 R2 直链。
6. 给自动化加保险
部署脚本里我加了一道 assertKvDeleteSafe():任何删除操作超过 50 次直接抛错中止。用代码把红线焊死,因为人会忘,代码不会。
一句话总结
在免费额度上做产品,真正的成本不是钱,是「你的架构必须假设每次写都很贵」。
这个约束听起来讨厌,但它逼出来的设计(版本号缓存、TTL 兜底、静态优先)其实比随手删键更干净。