AI 参与说明(Agent:Codex
/root):本文由 Agent 根据 Cloudflare 官方产品页、开发者文档与公开类型定义辅助整理,并对示例、链接和 Hugo 产物进行校验。资料核验日期为 2026-08-24;价格、限额、Beta 状态和 API 可能继续变化,部署前请复核文末官方来源。
Cloudflare Workers KV 的定位很明确:它是一个面向全球低延迟读取、内置多层缓存、最终一致的持久化 key-value store。最合适的负载是“少写、多读、允许短时间旧值”,例如应用配置、feature flags、路由表、用户偏好和可重建缓存。
它不是通用的 Redis、关系数据库或强一致协调器。余额、库存、座位、计数器、锁、立即生效的权限撤销,以及任何依赖原子 read-modify-write 的状态,不应直接放在 KV 中完成。此类需求通常应选择 Durable Objects 或 D1,再把适合全球分发的读模型投影到 KV。
KV 的架构:中心持久化,按访问位置缓存#
写入 KV 时,数据先进入少量 central data stores,并不会立即推送到 Cloudflare 的每一个 location。某地第一次读取一个 key 时,可能依次经过 local cache、regional tier、central tier,最后访问 central stores;结果会沿读取路径缓存,后续同地的 hot read 才能获得最低延迟。How KV works
flowchart TB
W["Worker binding / REST API 写入"] --> S[("Central data stores")]
R["某个 Cloudflare location 发起读取"] --> L{"Local cache 命中?"}
L -->|是| O["返回 hot value"]
L -->|否| G{"Regional / central cache 命中?"}
G -->|是| F["向下层回填缓存"]
G -->|否| S
S --> F
F --> O这个架构同时解释了 KV 的优势与边界:热点读取可以非常快,冷 key 的第一次读取可能明显更慢;缓存让全球读吞吐容易扩展,也让写入后的读取只能做到 eventual consistency。Cloudflare 产品页给出的 “sub-5 ms hot read” 描述针对 hot read,不应当被理解成每次读取的延迟保证。Workers KV 产品页
一致性:60 秒不是强一致承诺#
KV 最容易被误解的是“写成功”和“全球读到新值”之间的差别:
| 行为 | 应采用的工程假设 |
|---|---|
| 写入发生地的后续读取 | 通常很快可见,但 Cloudflare 明确说明这并不保证;不要依赖 read-your-writes。 |
| 其他 location 的读取 | 可能在 60 秒或更久后才看到新值,具体还受既有缓存与 cacheTtl 影响。 |
| 读取不存在的 key | negative lookup 也会被缓存;刚创建的 key 仍可能暂时返回 null。 |
| 同一 key 的并发写入 | last write wins,竞争写可能互相覆盖;同一 key 最多每秒写 1 次,超出会收到 429。 |
| 多 key 更新 | 没有跨 key transaction,也没有原子 compare-and-set 或 read-modify-write。 |
cacheTtl 控制某次读取结果在访问 location 的缓存时长。当前最小值是 30 秒,默认值是 60 秒;调大它可以减少冷读取,却也会延长其他地方看到旧值的窗口。不存在的 key 同样会被缓存。读取 API
不要把 cacheTtl 和 expirationTtl 混为一谈:
| 参数 | 所属操作 | 控制什么 | 当前最小值 |
|---|---|---|---|
cacheTtl | get() / getWithMetadata() | 读取结果在访问 location 的缓存时长 | 30 秒 |
expirationTtl | put() | key 从写入起还能存活多久 | 60 秒 |
key 到达 expiration 后会从系统删除;即使读取使用了更长的 cacheTtl,expiration 仍然优先。写入与 expiration
适合与不适合的场景#
| 需求 | 是否适合 KV | 原因或替代方案 |
|---|---|---|
| 应用配置、feature flags、A/B test 参数 | 适合 | 读多写少;业务必须接受配置在全球分批生效。 |
| 路由映射、redirect rules、慢变 allow-list | 适合 | key lookup 简单,hot read 延迟低;撤销窗口必须被显式接受。 |
| 用户偏好、公开 JSON、可重建 API cache | 适合 | 短暂旧值通常可容忍,可用 TTL 管理生命周期。 |
| 基本不再修改的版本化对象或小型静态资源 | 适合 | immutable key 可以避开大部分一致性冲突。 |
| 余额、库存、抢购、座位、锁、全局计数器 | 不适合 | 需要原子更新和强一致协调;使用 Durable Objects 或数据库。 |
| 高频更新同一个 key | 不适合 | 同 key 每秒只能写 1 次,且 competing writes 会覆盖。 |
| 必须立即生效的 token / 权限撤销 | 不适合单独使用 | 旧授权结果可能继续命中缓存;使用强一致 authority,或明确接受撤销窗口。 |
| SQL、二级索引、聚合和多条件查询 | 不适合 | list() 只有 prefix、cursor 与字节序排序;使用 D1 或其他数据库。 |
| 大文件、媒体、备份 | 不适合 | 单 value 上限 25 MiB;使用 R2。 |
Cloudflare 官方也把 authentication token lookup 列为示例场景,但这只说明 KV 能快速查 token。由其一致性模型可以推断,它不满足“全球立即撤销”的安全语义。采用 KV 做鉴权读模型时,应把最大陈旧窗口、token 自身过期时间和强一致撤销通道一起纳入设计。
和其他 Cloudflare 数据能力怎么选#
| 核心需求 | 首选能力 |
|---|---|
| 全球读多写少的 key-value 数据,可接受 eventual consistency | Workers KV |
| 单个业务 key 下的顺序协调、原子状态、WebSocket room | Durable Objects |
| SQL、关系、索引、事务和报表查询 | D1 |
| 图片、文件、大对象与归档 | R2 |
| 仅缓存 HTTP response,不把缓存当持久数据源 | Workers Cache API |
KV 是一种持久数据服务;Cache API 是面向 HTTP response、按数据中心工作的缓存接口。两者都能加速读取,但生命周期、寻址方式和一致性语义不同,不能互换。Workers 中 binding 与 runtime API 的整体边界可先阅读 Workers:运行时 API、Bindings 与执行模型。
具体案例:应用的 AI 模型列表#
如果一个应用要维护数十个 AI 模型的 ID、provider、capabilities、启用状态与排序,默认更适合使用 KV。这类 model catalog 通常由后台或 CI 低频发布,却会在每个请求中高频读取,正好匹配 KV 的“少写、多读、全球缓存”特征。
小型目录可以整体存为一个 JSON value,而不是在请求路径上执行 list():
{
"schemaVersion": 1,
"catalogVersion": 42,
"updatedAt": "2026-08-24T12:00:00Z",
"models": [
{
"id": "example-chat-model",
"provider": "example-provider",
"capabilities": ["text", "tools"],
"enabled": true,
"order": 10
}
]
}这种结构只需一次 get("model-catalog:current", "json"),一次 put() 也会替换完整目录,避免读者看到同一个 value 内的半成品。但新旧 value 仍可能因 eventual consistency 在不同 location 并存,因此应由单一发布入口让 catalogVersion 单调递增,读取端也要能接受短暂旧版本。
| 模型目录要求 | 建议 |
|---|---|
| 后台偶尔更新,允许不同地区在一段时间内看到旧列表 | 只用 KV,整体发布一个 versioned JSON document。 |
| 多个管理员可能同时编辑,需要版本检查、校验或原子更新 | 用单个 Durable Object 作为 model catalog authority。 |
| 模型停用后必须立即阻止新请求 | 请求路径查询 Durable Object 或另一个强一致 authority;不能只信任 KV 的 enabled 字段。 |
| provider 健康状态、实时限流、failover weight 会频繁变化 | 把运行时状态放在 Durable Object 或专用路由层;KV 只保存慢变的目录与默认值。 |
| 既需要强一致管理,又需要全球低延迟读取 | Durable Object 保存事实源,每次成功更新后再向 KV 发布完整快照。 |
| 需要按 provider、capability、tenant 做组合查询、审计与历史报表 | 优先用 D1 建模,再视读取流量决定是否发布 KV 读模型。 |
仅用 Durable Object 读取虽然可以得到强一致结果,但请求必须路由到该唯一 object,不再具有 KV 的全球 hot-read 特征。混合方案中,Durable Object 先完成校验与持久化,再发布例如 model-catalog:v42 的 immutable snapshot 和 model-catalog:current manifest。manifest 仍然是 eventual consistency,所以这是“强一致管理面 + 最终一致读取面”,不是让 KV 变成强一致。Use Workers KV from Durable Objects
最小可运行示例:用 KV 分发 feature flag#
下面采用“单一管理入口写入、全球 Worker 只读”的模式。它比在所有请求中同时读写同一个 key 更贴合 KV 的设计。
1. 创建 namespace 与 binding#
在已有 Worker 项目中执行:
npx wrangler kv namespace create FEATURE_FLAGS命令会创建远程 namespace 并输出 ID。把 ID 写入 wrangler.jsonc:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "kv-feature-flags",
"main": "src/index.ts",
"compatibility_date": "2026-08-24",
"kv_namespaces": [
{
"binding": "FEATURE_FLAGS",
"id": "<YOUR_KV_NAMESPACE_ID>"
}
]
}binding 是 Worker 代码中的变量名,namespace ID 才指向实际资源。staging 与 production 应使用不同 namespace,但可以保留同一个 binding name;Cloudflare 支持在 Wrangler environments 中分别声明 ID。Getting started Environments
配置完成后运行 npx wrangler types,让生成的 Env 类型与实际 binding 保持一致。为便于单文件阅读,下面仍显式写出 Env:
type FeatureFlag = {
enabled: boolean;
rollout: number;
};
type FlagMetadata = {
schemaVersion: 1;
updatedAt: string;
};
interface Env {
FEATURE_FLAGS: KVNamespace;
}
export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
const match = /^\/flags\/([a-z0-9-]+)$/.exec(url.pathname);
if (request.method !== "GET" || match === null) {
return new Response("Not Found", { status: 404 });
}
const name = match[1];
const result = await env.FEATURE_FLAGS.getWithMetadata<
FeatureFlag,
FlagMetadata
>(`flag:${name}`, {
type: "json",
cacheTtl: 60,
});
if (result.value === null) {
return Response.json({ error: "flag not found" }, { status: 404 });
}
return Response.json(
{
name,
value: result.value,
metadata: result.metadata,
},
{
headers: {
"Cache-Control": "no-store",
},
},
);
},
} satisfies ExportedHandler<Env>;2. 写入本地测试数据并验证#
wrangler dev 默认使用隔离的本地 KV,不会读取或修改远程 namespace。先写入本地数据:
npx wrangler kv key put \
--binding=FEATURE_FLAGS \
"flag:checkout" \
'{"enabled":true,"rollout":25}' \
--metadata='{"schemaVersion":1,"updatedAt":"2026-08-24T00:00:00Z"}' \
--local
npx wrangler dev另开终端请求:
curl http://localhost:8787/flags/checkout预期得到等价 JSON:
{
"name": "checkout",
"value": {
"enabled": true,
"rollout": 25
},
"metadata": {
"schemaVersion": 1,
"updatedAt": "2026-08-24T00:00:00Z"
}
}需要明确操作远程 namespace 时才添加 --remote,或在 binding 上配置 "remote": true。远程读写会计入用量,也可能影响线上数据;不要为了让本地示例“读得到”而默认连接 production。Local development 行为
Workers Binding API 地图#
| 操作 | 常用接口 | 关键语义 |
|---|---|---|
| 读单个 key | get(key, type/options) | 缺失时返回 null;可读成 text、json、arrayBuffer 或 stream。 |
| 读多个 key | get(keys, type/options) | 最多 100 个 key,只支持 text / json;总 response 上限 25 MB。 |
| 连 metadata 读取 | getWithMetadata() | 返回 { value, metadata };同样可能读到旧值。 |
| 写入 | put(key, value, options) | 可设置 expiration、expirationTtl 与最多 1024 bytes 的 JSON metadata。 |
| 删除 | delete(key) | 删除不存在的 key 也成功;其他 location 仍可能暂时读到旧值。 |
| 列举 key | list({ prefix, limit, cursor }) | 每页最多 1,000 个,按 UTF-8 bytes 字典序返回;用 list_complete 判断是否结束。 |
单次 Worker invocation 最多进行 1,000 次 external service operations,这一额度由 KV、R2 等外部服务调用共享。一次 bulk read 在这个调用额度中算 1 次,但计费仍按读取的 key 数量计算。Workers binding 不提供 bulk write;Wrangler 或 REST API 的 bulk write 一次最多 10,000 个 key-value pairs,整个 request 必须小于 100 MB。读取 API 写入 API List API
数据建模与生产模式#
1. 先按隔离边界拆 namespace#
namespace 应表达真正的运维与隔离边界,而不是模拟关系数据库的每张表。可以把拆分公式概括为:
namespace = environment × trust/owner × lifecycle × jurisdiction
key prefix = tenant × domain × entity × version| 边界 | 是否应拆 namespace | 原因 |
|---|---|---|
| production、staging、development | 必须拆 | 防止测试、回填和清理脚本触及线上数据。 |
| 不同 Worker 的信任或所有权边界 | 通常应拆 | 只给需要数据的 Worker 注入 binding;key prefix 不是授权边界。 |
| 长期配置与可丢弃 runtime cache | 按需拆 | 当负责人、批量清理、迁移、发布路径或告警策略独立时再拆;TTL 是 per-key 能力,不构成单独拆分理由。 |
| 不同法规数据位置 | 必须拆 | Jurisdiction 只能在 namespace 创建时设定,之后不能添加或修改。 |
| provider、模型、功能或版本 | 通常不拆 | 使用 key prefix 和 versioned key 即可。 |
| 大量 tenant,访问策略与生命周期相同 | 默认不拆 | 使用 tenant:<id>: prefix;必须在 Worker 中做授权校验。 |
Cloudflare 当前限制每个 account 最多 1,000 个 namespaces,但每个 namespace 的 key 数量是 Unlimited。因此“每个 tenant 或每个模型一个 namespace”会很快形成 namespace explosion,也会让 binding、部署和监控难以管理。namespace ID 是 public identifier,不是 credential;真正的授权仍由 Worker 逻辑、binding 暴露范围与 API Token 权限完成。KV limits KV namespaces
对上文的 model catalog,推荐的 namespace title 命名规则是 <app>-<environment>-<data-domain>[-<jurisdiction>]:
model-router-development-model-catalog
model-router-staging-model-catalog
model-router-production-model-catalog
model-router-development-runtime-cache
model-router-staging-runtime-cache
model-router-production-runtime-cachetitle 主要服务于 Dashboard、CLI 和运维识别;代码中的 binding 应使用稳定的语义名,例如 MODEL_CATALOG 和 RUNTIME_CACHE,不在 binding 中加 PROD_ 或 STAGING_。这样同一份 Worker 代码在各环境中都使用 env.MODEL_CATALOG,只由 Wrangler 配置切换 namespace ID。KV environments
可先创建三个独立 namespace:
npx wrangler kv namespace create model-router-development-model-catalog
npx wrangler kv namespace create model-router-staging-model-catalog
npx wrangler kv namespace create model-router-production-model-catalog然后把返回的 ID 显式写入各 environment:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "model-router",
"main": "src/index.ts",
"compatibility_date": "2026-08-24",
"kv_namespaces": [
{
"binding": "MODEL_CATALOG",
"id": "<DEVELOPMENT_NAMESPACE_ID>"
}
],
"env": {
"staging": {
"kv_namespaces": [
{
"binding": "MODEL_CATALOG",
"id": "<STAGING_NAMESPACE_ID>"
}
]
},
"production": {
"kv_namespaces": [
{
"binding": "MODEL_CATALOG",
"id": "<PRODUCTION_NAMESPACE_ID>"
}
]
}
}
}kv_namespaces 是 Wrangler environment 的 non-inheritable 配置;一个 environment 需要多个 KV binding 时,要在该 environment 中完整重复所有条目,不能假设它会从顶层继承。wrangler dev 默认使用本地 KV,即使配置中有 namespace ID 也不会直接读写远程数据。只在集成测试确实需要时,才对专用 development namespace 设置 "remote": true;不要把本地 Worker 指向 production ID。KV bindings Wrangler environments
同理,--env=staging 只负责选择 staging 配置,不表示“使用远程数据”。当 wrangler kv key put/get/list/delete 的目标是 Cloudflare 上的真实 namespace 时,应显式传入 --remote,例如 npx wrangler kv key put --env=staging --binding=MODEL_CATALOG --remote "catalog:current" "<JSON>"。这个显式标志能避免操作者把 local、staging 和 production 的对象混为一谈。Wrangler KV commands
普通 multi-tenant 应用应先用稳定、不可变的 tenant ID 组成 key prefix。只有在 tenant 数量明确受控,且确实需要独立授权、整体删除、Jurisdiction 或故障域时,才考虑 namespace-per-tenant;即便如此,namespace title 和 ID 也不能替代 Worker 的 tenant 授权检查。
Workers KV Jurisdictions 截至核验日仍是 private beta。它只约束 durable storage location,数据仍可能缓存在 jurisdiction 之外;因此在 namespace title 中加 eu、us 或 fedramp 只是运维约定,必须与创建时的实际 jurisdiction 参数和完整合规方案一起验证。Data location
2. 让 key 表达逻辑命名空间与版本#
KV 没有表和二级索引,通常用可预测的 prefix 组织 key:
config:checkout:v3
config:search:v8
tenant:acme:preferences
cache:catalog:2026-08-24把基本不变的数据写到 versioned key,再最后更新一个 manifest,可以减少多 key 发布时“新旧字段混在一起”的概率。不过 manifest 自身仍是 eventual consistency,不能把这种模式称为 transaction。
3. 单写者,多读者#
对同一个逻辑 key,优先让 CI、管理 API 或对应的 Durable Object 成为唯一写入路径。这样可以避免 competing writes;若同 key 写入仍可能撞上每秒 1 次限制,应合并更新并对 429 做带抖动的 exponential backoff。不要在每个全球请求中执行“先读、修改、再写”的非原子流程。
写入成功后,响应应直接返回本次已知的新值,不要马上重新读取 KV 来“确认”写入;重新读取可能命中旧值或 negative cache。
4. 合并相关小值,但保留更新边界#
把总是一起读取的多个小字段合并成一个 JSON value,可以减少操作数并让冷字段随热点字段一起缓存。代价是更新其中一个字段也要重写整个对象,并引入 read-modify-write 冲突。需要独立、高频更新的字段不应为了少一次读取而强行合并。
5. 不把 list() 当查询引擎#
list() 适合管理工具、prefix 扫描和离线任务。它没有任意条件过滤、聚合或 secondary index,而且每次 list 都计费。线上请求若依赖完整扫描,应改为维护明确索引、使用 D1,或重新设计访问模式。
当前限额与价格#
以下数据按 2026-08-24 的官方页面整理。Cloudflare 的 Limits 页面中,Paid 的 “Unlimited” 表示没有 Free plan 那样的每日操作/存储配额,并不表示超出套餐包含量后免费。
固定技术限额#
| 项目 | Free / Paid |
|---|---|
| 同一 key 写入 | 每秒 1 次 |
| 每次 Worker invocation 的 external service operations | 1,000 次 |
| 每账号 namespaces | 1,000 个 |
| key 长度 | 512 bytes |
| value 大小 | 25 MiB |
| metadata 大小 | 1,024 bytes |
| 单 namespace 的 key 数量 | Unlimited |
最小 cacheTtl | 30 秒 |
Free plan 的 storage/account 和 storage/namespace 均为 1 GB;Paid plan 当前列为 Unlimited。需要更高的可调整限额时,可通过官方 limit increase form 申请。KV limits
套餐包含量与超额价格#
| 项目 | Free plan | Paid plan 包含量与超额价格 |
|---|---|---|
| 读取 | 100,000 keys / day | 10 million / month,之后 $0.50 / million |
| 写入 | 1,000 keys / day | 1 million / month,之后 $5.00 / million |
| 删除 | 1,000 keys / day | 1 million / month,之后 $5.00 / million |
| List requests | 1,000 / day | 1 million / month,之后 $5.00 / million |
| 存储 | 1 GB | 1 GB,之后 $0.50 / GB-month |
Free plan 配额在每天 00:00 UTC 重置,某一类操作超额后,该类后续操作会失败。读取不存在的 key 也算一次读取;Dashboard 与 Wrangler 操作同样计费;bulk read 按 key 数量计费。KV 当前不收 data transfer / egress 费,但 Worker compute 与其他产品费用需要另算。KV pricing
安全、数据位置与可观测性#
- Cloudflare 表示 KV value 会自动使用 AES-256 加密 at rest,当前优先采用 GCM;Worker、Cloudflare 内部节点、HTTP API 与 Wrangler 的传输使用 TLS。这里的 encryption key 由 Cloudflare 管理;需要应用层密钥控制时,应在写入前自行加密。Data security
- Worker 内优先使用 binding,让资源访问能力随部署配置注入;外部自动化使用最小权限 API Token,不要在代码或仓库中放 Global API key。部署本身的 secret 应使用 Workers Secrets 或 Secrets Store,而不是普通 KV value。
- 默认情况下,数据会全球复制。Workers KV Jurisdictions 截至核验日仍是 private beta;即使限制了 namespace 的 durable storage location,KV data 仍可能缓存在 jurisdiction 之外。合规设计不能只看 namespace 创建参数。Data location
- Dashboard 和 GraphQL Analytics API 提供 per-namespace operations、storage 与 read latency 数据,当前保留 31 天。生产告警还应覆盖
429、读写错误、冷读取长尾、Free plan 配额耗尽,以及应用侧 fallback 是否持续触发;应用层 missing-key rate 需要自行埋点。Metrics and analytics
上线前检查清单#
- 业务已定义可接受的陈旧阈值,并在超过阈值时具备 fallback 或强一致查询路径;没有把“通常 60 秒”当作硬性 SLA 上界。
- 余额、库存、权限撤销、锁和计数器等强一致状态不以 KV 为唯一事实来源。
- staging、production 使用不同 namespace;本地默认使用 local KV。
- 同一 key 有单写者,高频数据已拆 key、聚合或迁移到 Durable Objects。
cacheTtl、key expiration 与业务 TTL 分别设计,没有混用。- key 命名、JSON schema、metadata version、空值与
nullfallback 均有约定。 - 已估算 missing-key reads、Dashboard / Wrangler 操作与 bulk per-key billing。
- 已监控 operation、storage、latency、
429和配额失败,并准备降级路径。
小结#
Workers KV 的核心价值不是“又一个 key-value API”,而是把持久数据按访问热度带到 Cloudflare 的全球缓存层。只要业务能接受 eventual consistency,并把写入集中、key 版本化、陈旧窗口与失败降级设计清楚,它非常适合配置与读模型分发;一旦需求变成原子更新、高频同 key 写入或立即撤销,就应把强一致事实来源放在 Durable Objects、D1 或其他数据库中,再决定是否把只读投影同步到 KV。