cloudflare-kv
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCloudflare Workers KV
Cloudflare Workers KV
Status: Production Ready ✅
Last Updated: 2026-01-20
Dependencies: cloudflare-worker-base (for Worker setup)
Latest Versions: wrangler@4.59.2, @cloudflare/workers-types@4.20260109.0
Recent Updates (2025):
- August 2025: Architecture redesign (40x performance gain, <5ms p99 latency, hybrid storage with R2)
- April 2025: Bulk reads API (retrieve up to 100 keys in single request, counts as 1 operation)
- January 2025: Namespace limit increased (200 → 1,000 namespaces per account for Free and Paid plans)
状态: 已就绪可用于生产 ✅
最后更新: 2026-01-20
依赖项: cloudflare-worker-base(用于Worker搭建)
最新版本: wrangler@4.59.2, @cloudflare/workers-types@4.20260109.0
2025年更新记录:
- 2025年8月: 架构重构(性能提升40倍,p99延迟<5ms,搭配R2的混合存储)
- 2025年4月: 批量读取API(单次请求最多获取100个键,仅计为1次操作)
- 2025年1月: 命名空间上限提升(免费版和付费版账户的命名空间数量从200个增加至1000个)
Quick Start (5 Minutes)
快速入门(5分钟)
bash
undefinedbash
undefinedCreate namespace
创建命名空间
npx wrangler kv namespace create MY_NAMESPACE
npx wrangler kv namespace create MY_NAMESPACE
Output: [[kv_namespaces]] binding = "MY_NAMESPACE" id = "<UUID>"
输出: [[kv_namespaces]] binding = "MY_NAMESPACE" id = "<UUID>"
**wrangler.jsonc:**
```jsonc
{
"kv_namespaces": [{
"binding": "MY_NAMESPACE", // Access as env.MY_NAMESPACE
"id": "<production-uuid>",
"preview_id": "<preview-uuid>" // Optional: local dev
}]
}Basic Usage:
typescript
type Bindings = { MY_NAMESPACE: KVNamespace };
app.post('/set/:key', async (c) => {
await c.env.MY_NAMESPACE.put(c.req.param('key'), await c.req.text());
return c.json({ success: true });
});
app.get('/get/:key', async (c) => {
const value = await c.env.MY_NAMESPACE.get(c.req.param('key'));
return value ? c.json({ value }) : c.json({ error: 'Not found' }, 404);
});
**wrangler.jsonc:**
```jsonc
{
"kv_namespaces": [{
"binding": "MY_NAMESPACE", // 通过env.MY_NAMESPACE访问
"id": "<production-uuid>",
"preview_id": "<preview-uuid>" // 可选:本地开发用
}]
}基础用法:
typescript
type Bindings = { MY_NAMESPACE: KVNamespace };
app.post('/set/:key', async (c) => {
await c.env.MY_NAMESPACE.put(c.req.param('key'), await c.req.text());
return c.json({ success: true });
});
app.get('/get/:key', async (c) => {
const value = await c.env.MY_NAMESPACE.get(c.req.param('key'));
return value ? c.json({ value }) : c.json({ error: '未找到' }, 404);
});KV API Reference
KV API 参考
Read Operations
读取操作
typescript
// Get single key
const value = await env.MY_KV.get('key'); // string | null
const data = await env.MY_KV.get('key', { type: 'json' }); // object | null
const buffer = await env.MY_KV.get('key', { type: 'arrayBuffer' });
const stream = await env.MY_KV.get('key', { type: 'stream' });
// Get with cache (minimum 60s)
const value = await env.MY_KV.get('key', { cacheTtl: 300 }); // 5 min edge cache
// Bulk read (counts as 1 operation)
const values = await env.MY_KV.get(['key1', 'key2']); // Map<string, string | null>
// With metadata
const { value, metadata } = await env.MY_KV.getWithMetadata('key');
const result = await env.MY_KV.getWithMetadata(['key1', 'key2']); // Bulk with metadatatypescript
// 获取单个键
const value = await env.MY_KV.get('key'); // string | null
const data = await env.MY_KV.get('key', { type: 'json' }); // object | null
const buffer = await env.MY_KV.get('key', { type: 'arrayBuffer' });
const stream = await env.MY_KV.get('key', { type: 'stream' });
// 带缓存获取(最小60秒)
const value = await env.MY_KV.get('key', { cacheTtl: 300 }); // 5分钟边缘缓存
// 批量读取(计为1次操作)
const values = await env.MY_KV.get(['key1', 'key2']); // Map<string, string | null>
// 带元数据
const { value, metadata } = await env.MY_KV.getWithMetadata('key');
const result = await env.MY_KV.getWithMetadata(['key1', 'key2']); // 批量获取带元数据Write Operations
写入操作
typescript
// Basic write (max 1/second per key)
await env.MY_KV.put('key', 'value');
await env.MY_KV.put('user:123', JSON.stringify({ name: 'John' }));
// With expiration
await env.MY_KV.put('session', data, { expirationTtl: 3600 }); // 1 hour
await env.MY_KV.put('token', value, { expiration: Math.floor(Date.now()/1000) + 86400 });
// With metadata (max 1024 bytes)
await env.MY_KV.put('config', 'dark', {
metadata: { updatedAt: Date.now(), version: 2 }
});Critical Limits:
- Key: 512 bytes max
- Value: 25 MiB max
- Metadata: 1024 bytes max
- Write rate: 1/second per key (429 error if exceeded)
- Expiration: 60 seconds minimum
typescript
// 基础写入(每个键每秒最多1次)
await env.MY_KV.put('key', 'value');
await env.MY_KV.put('user:123', JSON.stringify({ name: 'John' }));
// 带过期时间
await env.MY_KV.put('session', data, { expirationTtl: 3600 }); // 1小时
await env.MY_KV.put('token', value, { expiration: Math.floor(Date.now()/1000) + 86400 });
// 带元数据(最大1024字节)
await env.MY_KV.put('config', 'dark', {
metadata: { updatedAt: Date.now(), version: 2 }
});关键限制:
- 键:最大512字节
- 值:最大25 MiB
- 元数据:最大1024字节
- 写入速率:每个键每秒1次(超过会触发429错误)
- 过期时间:最小60秒
List Operations
列表操作
typescript
// List with pagination
const result = await env.MY_KV.list({ prefix: 'user:', limit: 1000, cursor });
// result: { keys: [], list_complete: boolean, cursor?: string }
// CRITICAL: Always check list_complete, not keys.length === 0
let cursor: string | undefined;
do {
const result = await env.MY_KV.list({ prefix: 'user:', cursor });
processKeys(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);typescript
// 分页列出
const result = await env.MY_KV.list({ prefix: 'user:', limit: 1000, cursor });
// result: { keys: [], list_complete: boolean, cursor?: string }
// 重要:始终检查list_complete,而不是keys.length === 0
let cursor: string | undefined;
do {
const result = await env.MY_KV.list({ prefix: 'user:', cursor });
processKeys(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);Delete Operations
删除操作
typescript
// Delete single key
await env.MY_KV.delete('key'); // Always succeeds
// Bulk delete (CLI only, up to 10,000 keys)
// npx wrangler kv bulk delete --binding=MY_KV keys.jsontypescript
// 删除单个键
await env.MY_KV.delete('key'); // 始终执行成功
// 批量删除(仅支持CLI,最多10000个键)
// npx wrangler kv bulk delete --binding=MY_KV keys.jsonAdvanced Patterns
高级模式
Caching Pattern with CacheTtl
结合CacheTtl的缓存模式
typescript
async function getCachedData(kv: KVNamespace, key: string, fetchFn: () => Promise<any>, ttl = 300) {
const cached = await kv.get(key, { type: 'json', cacheTtl: ttl });
if (cached) return cached;
const data = await fetchFn();
await kv.put(key, JSON.stringify(data), { expirationTtl: ttl * 2 });
return data;
}Guidelines: Minimum 60s, use for read-heavy workloads (100:1 read/write ratio)
typescript
async function getCachedData(kv: KVNamespace, key: string, fetchFn: () => Promise<any>, ttl = 300) {
const cached = await kv.get(key, { type: 'json', cacheTtl: ttl });
if (cached) return cached;
const data = await fetchFn();
await kv.put(key, JSON.stringify(data), { expirationTtl: ttl * 2 });
return data;
}指南: 最小60秒,适用于读密集型工作负载(读写比100:1)
Metadata Optimization
元数据优化
typescript
// Store small values (<1024 bytes) in metadata to avoid separate get() calls
await env.MY_KV.put('user:123', '', {
metadata: { status: 'active', plan: 'pro', lastSeen: Date.now() }
});
// list() returns metadata automatically (no additional get() calls)
const users = await env.MY_KV.list({ prefix: 'user:' });
users.keys.forEach(({ name, metadata }) => console.log(name, metadata.status));typescript
// 将小值(<1024字节)存储在元数据中,避免额外的get()调用
await env.MY_KV.put('user:123', '', {
metadata: { status: 'active', plan: 'pro', lastSeen: Date.now() }
});
// list()会自动返回元数据(无需额外get()调用)
const users = await env.MY_KV.list({ prefix: 'user:' });
users.keys.forEach(({ name, metadata }) => console.log(name, metadata.status));Understanding Hot vs Cold Keys
理解热键与冷键
KV performance varies based on key temperature:
| Type | Response Time | When It Happens |
|---|---|---|
| Hot keys | 6-8ms | Read 2+ times/minute per datacenter |
| Cold keys | 100-300ms | Infrequently accessed, fetched from central storage |
Post-August 2025 Improvements:
- P90 for all KV Worker invocations: <12ms (was 22ms before)
- Hot reads up to 3x faster
- All operations faster by up to 20ms
Optimization: Use key coalescing to make cold keys benefit from hot key caching:
typescript
// ❌ Bad: Many cold keys (300ms each)
await kv.put('user:123:name', 'John');
await kv.put('user:123:email', 'john@example.com');
await kv.put('user:123:plan', 'pro');
// Each read of a cold key: ~100-300ms
const name = await kv.get('user:123:name'); // Cold
const email = await kv.get('user:123:email'); // Cold
const plan = await kv.get('user:123:plan'); // Cold
// ✅ Good: Single hot key (6-8ms)
await kv.put('user:123', JSON.stringify({
name: 'John',
email: 'john@example.com',
plan: 'pro'
}));
// Single read, cached as hot key: ~6-8ms
const user = JSON.parse(await kv.get('user:123'));CacheTtl helps cold keys: For infrequently-read data, reduces cold read latency.
cacheTtlTrade-off: Coalescing requires read-modify-write for updates
KV性能因键的热度而异:
| 类型 | 响应时间 | 触发场景 |
|---|---|---|
| 热键 | 6-8ms | 每个数据中心每分钟被读取2次以上 |
| 冷键 | 100-300ms | 访问频率低,从中央存储获取 |
2025年8月改进:
- 所有KV Worker调用的P90延迟:<12ms(之前为22ms)
- 热键读取速度提升3倍
- 所有操作速度最多提升20ms
优化建议: 使用键合并让冷键受益于热键缓存:
typescript
// ❌ 不佳:多个冷键(每个300ms)
await kv.put('user:123:name', 'John');
await kv.put('user:123:email', 'john@example.com');
await kv.put('user:123:plan', 'pro');
// 每个冷键读取:~100-300ms
const name = await kv.get('user:123:name'); // 冷键
const email = await kv.get('user:123:email'); // 冷键
const plan = await kv.get('user:123:plan'); // 冷键
// ✅ 良好:单个热键(6-8ms)
await kv.put('user:123', JSON.stringify({
name: 'John',
email: 'john@example.com',
plan: 'pro'
}));
// 单次读取,作为热键缓存:~6-8ms
const user = JSON.parse(await kv.get('user:123'));CacheTtl帮助冷键: 对于访问频率低的数据,可降低冷读取延迟。
cacheTtl权衡: 合并键需要在更新时执行读取-修改-写入操作
Pagination Helper
分页助手
typescript
async function* paginateKV(kv: KVNamespace, options: { prefix?: string } = {}) {
let cursor: string | undefined;
do {
const result = await kv.list({ ...options, cursor });
yield result.keys;
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
}
// Usage
for await (const keys of paginateKV(env.MY_KV, { prefix: 'user:' })) {
processKeys(keys);
}typescript
async function* paginateKV(kv: KVNamespace, options: { prefix?: string } = {}) {
let cursor: string | undefined;
do {
const result = await kv.list({ ...options, cursor });
yield result.keys;
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
}
// 使用方式
for await (const keys of paginateKV(env.MY_KV, { prefix: 'user:' })) {
processKeys(keys);
}Rate Limit Retry with Exponential Backoff
带指数退避的速率限制重试
typescript
async function putWithRetry(kv: KVNamespace, key: string, value: string, opts?: KVPutOptions) {
let attempts = 0, delay = 1000;
while (attempts < 5) {
try {
await kv.put(key, value, opts);
return;
} catch (error) {
if ((error as Error).message.includes('429')) {
attempts++;
if (attempts >= 5) throw new Error('Max retry attempts');
await new Promise(r => setTimeout(r, delay));
delay *= 2; // Exponential backoff
} else throw error;
}
}
}typescript
async function putWithRetry(kv: KVNamespace, key: string, value: string, opts?: KVPutOptions) {
let attempts = 0, delay = 1000;
while (attempts < 5) {
try {
await kv.put(key, value, opts);
return;
} catch (error) {
if ((error as Error).message.includes('429')) {
attempts++;
if (attempts >= 5) throw new Error('达到最大重试次数');
await new Promise(r => setTimeout(r, delay));
delay *= 2; // 指数退避
} else throw error;
}
}
}Understanding Eventual Consistency
理解最终一致性
KV is eventually consistent across Cloudflare's global network (Aug 2025 redesign: hybrid storage, <5ms p99 latency):
How It Works:
- Writes immediately visible in same location (read-your-own-write consistency within same POP)
- Other locations see update within ~60 seconds (or cacheTtl value)
- Cached reads may return stale data during propagation
Example:
typescript
// Tokyo: Write
await env.MY_KV.put('counter', '1');
const value = await env.MY_KV.get('counter'); // "1" ✅ (same POP, RYOW)
// London (within 60s): May be stale ⚠️
const value2 = await env.MY_KV.get('counter'); // Might be old value
// After 60+ seconds: Consistent ✅Read-Your-Own-Write (RYOW) Guarantee: Since August 2025 redesign, requests routed through the same Cloudflare point of presence see their own writes immediately. Global consistency across different POPs still takes up to 60 seconds.
Timestamp Mitigation Pattern (for critical consistency needs):
typescript
// Use timestamp in key structure to avoid consistency issues
const timestamp = Date.now();
await kv.put(`user:123:${timestamp}`, userData);
// Find latest using list with prefix
const result = await kv.list({ prefix: 'user:123:' });
const latestKey = result.keys.sort((a, b) =>
parseInt(b.name.split(':')[2]) - parseInt(a.name.split(':')[2])
).at(0);Use KV for: Read-heavy workloads (100:1 ratio), config, feature flags, caching, user preferences
Don't use KV for: Financial transactions, strong consistency, >1/second writes per key, critical data
Need strong consistency? Use Durable Objects
Source: Redesigning Workers KV
KV在Cloudflare全球网络中是最终一致的(2025年8月重构:混合存储,p99延迟<5ms):
工作原理:
- 写入操作在同一位置立即可见(同一POP内的读己写一致性)
- 其他位置会在约60秒内看到更新(或根据cacheTtl值)
- 缓存读取在传播期间可能返回旧数据
示例:
typescript
// 东京节点:写入
await env.MY_KV.put('counter', '1');
const value = await env.MY_KV.get('counter'); // "1" ✅(同一POP,读己写一致)
// 伦敦节点(60秒内):可能返回旧数据 ⚠️
const value2 = await env.MY_KV.get('counter'); // 可能是旧值
// 60秒后:数据一致 ✅读己写(RYOW)保证: 自2025年8月重构后,通过同一Cloudflare接入点路由的请求会立即看到自己的写入。不同POP之间的全局一致性仍需最多60秒。
时间戳缓解模式(针对关键一致性需求):
typescript
// 在键结构中使用时间戳避免一致性问题
const timestamp = Date.now();
await kv.put(`user:123:${timestamp}`, userData);
// 通过带前缀的list()查找最新数据
const result = await kv.list({ prefix: 'user:123:' });
const latestKey = result.keys.sort((a, b) =>
parseInt(b.name.split(':')[2]) - parseInt(a.name.split(':')[2])
).at(0);KV适用场景: 读密集型工作负载(100:1读写比)、配置存储、功能开关、缓存、用户偏好
KV不适用场景: 金融交易、强一致性需求、每个键每秒写入超过1次、关键数据
需要强一致性? 使用 Durable Objects
Wrangler CLI Essentials
Wrangler CLI 核心命令
bash
undefinedbash
undefinedCreate namespace
创建命名空间
npx wrangler kv namespace create MY_NAMESPACE [--preview]
npx wrangler kv namespace create MY_NAMESPACE [--preview]
Manage keys (add --remote flag to access production data)
管理键(添加--remote标志访问生产数据)
npx wrangler kv key put --binding=MY_KV "key" "value" [--ttl=3600] [--metadata='{}']
npx wrangler kv key get --binding=MY_KV "key" [--remote]
npx wrangler kv key list --binding=MY_KV [--prefix="user:"] [--remote]
npx wrangler kv key delete --binding=MY_KV "key"
npx wrangler kv key put --binding=MY_KV "key" "value" [--ttl=3600] [--metadata='{}']
npx wrangler kv key get --binding=MY_KV "key" [--remote]
npx wrangler kv key list --binding=MY_KV [--prefix="user:"] [--remote]
npx wrangler kv key delete --binding=MY_KV "key"
Bulk operations (up to 10,000 keys)
批量操作(最多10000个键)
npx wrangler kv bulk put --binding=MY_KV data.json
npx wrangler kv bulk delete --binding=MY_KV keys.json
**IMPORTANT**: CLI commands default to **local storage**. Add `--remote` flag to access production/remote data.
---npx wrangler kv bulk put --binding=MY_KV data.json
npx wrangler kv bulk delete --binding=MY_KV keys.json
**重要提示**: CLI命令默认使用**本地存储**。添加`--remote`标志以访问生产/远程数据。
---Development vs Production
开发环境 vs 生产环境
Remote Bindings for Local Development (Wrangler 4.37+)
本地开发的远程绑定(Wrangler 4.37+)
Connect local Workers to production KV namespaces during development:
wrangler.jsonc:
jsonc
{
"kv_namespaces": [{
"binding": "MY_KV",
"id": "production-uuid",
"remote": true // Connect to live KV
}]
}How It Works:
- Local Worker code executes locally (fast iteration)
- KV operations route to production namespace through proxy
- No manual data seeding required
Benefits:
- Test against real production data without deploying
- Fast local code execution with production data access
- Faster feedback loop (no deploy-test cycle)
⚠️ Warning: Writes affect production data. Consider using a staging namespace with instead of production.
remote: trueVersion Support:
- Wrangler 4.37.0+
- @cloudflare/vite-plugin 1.13.0+
- @cloudflare/vitest-pool-workers 0.9.0+
Source: Remote bindings architecture
开发期间将本地Worker连接到生产KV命名空间:
wrangler.jsonc:
jsonc
{
"kv_namespaces": [{
"binding": "MY_KV",
"id": "production-uuid",
"remote": true // 连接到线上KV
}]
}工作原理:
- 本地Worker代码在本地执行(迭代速度快)
- KV操作通过代理路由到生产命名空间
- 无需手动填充数据
优势:
- 无需部署即可测试真实生产数据
- 本地代码快速执行,同时可访问生产数据
- 更快的反馈循环(无需部署-测试周期)
⚠️ 警告: 写入操作会影响生产数据。建议使用 staging 命名空间并设置,而非直接使用生产环境。
remote: true版本支持:
- Wrangler 4.37.0+
- @cloudflare/vite-plugin 1.13.0+
- @cloudflare/vitest-pool-workers 0.9.0+
Limits & Quotas
限制与配额
| Feature | Free Plan | Paid Plan |
|---|---|---|
| Reads per day | 100,000 | Unlimited |
| Writes per day (different keys) | 1,000 | Unlimited |
| Writes per key per second | 1 | 1 |
| Operations per Worker invocation | 1,000 | 1,000 |
| Namespaces per account | 1,000 | 1,000 |
| Storage per account | 1 GB | Unlimited |
| Key size | 512 bytes | 512 bytes |
| Metadata size | 1024 bytes | 1024 bytes |
| Value size | 25 MiB | 25 MiB |
| Minimum cacheTtl | 60 seconds | 60 seconds |
Critical: 1 write/second per key (429 if exceeded), bulk operations count as 1 operation, namespace limit increased from 200 → 1,000 (Jan 2025)
| 功能 | 免费版 | 付费版 |
|---|---|---|
| 每日读取次数 | 100,000 | 无限制 |
| 每日写入次数(不同键) | 1,000 | 无限制 |
| 每个键每秒写入次数 | 1 | 1 |
| 单次Worker调用的操作次数 | 1,000 | 1,000 |
| 每个账户的命名空间数量 | 1,000 | 1,000 |
| 每个账户的存储容量 | 1 GB | 无限制 |
| 键大小 | 512字节 | 512字节 |
| 元数据大小 | 1024字节 | 1024字节 |
| 值大小 | 25 MiB | 25 MiB |
| 最小cacheTtl | 60秒 | 60秒 |
关键提示: 每个键每秒1次写入(超过会触发429速率限制错误),批量操作计为1次,命名空间上限从200提升至1000(2025年1月)
Error Handling
错误处理
1. Rate Limit (429 Too Many Requests)
1. 速率限制(429 Too Many Requests)
Cause: Writing to same key >1/second
Solution: Use retry with exponential backoff (see Advanced Patterns)
typescript
// ❌ Bad
await env.MY_KV.put('counter', '1');
await env.MY_KV.put('counter', '2'); // 429 error!
// ✅ Good
await putWithRetry(env.MY_KV, 'counter', '2');原因: 对同一键的写入超过每秒1次
解决方案: 使用带指数退避的重试机制(参考高级模式)
typescript
// ❌ 错误示例:触发速率限制
await env.MY_KV.put('counter', '1');
await env.MY_KV.put('counter', '2'); // 429错误!
// ✅ 正确示例
await putWithRetry(env.MY_KV, 'counter', '2');2. Value Too Large
2. 值过大
Cause: Value exceeds 25 MiB
Solution: Validate size before writing
typescript
if (value.length > 25 * 1024 * 1024) throw new Error('Value exceeds 25 MiB');原因: 值超过25 MiB
解决方案: 写入前验证大小
typescript
if (value.length > 25 * 1024 * 1024) throw new Error('值超过25 MiB限制');3. Metadata Too Large
3. 元数据过大
Cause: Metadata exceeds 1024 bytes when serialized
Solution: Validate serialized size
typescript
const serialized = JSON.stringify(metadata);
if (serialized.length > 1024) throw new Error('Metadata exceeds 1024 bytes');原因: 序列化后的元数据超过1024字节
解决方案: 验证序列化后的大小
typescript
const serialized = JSON.stringify(metadata);
if (serialized.length > 1024) throw new Error('元数据超过1024字节限制');4. Invalid CacheTtl
4. 无效的CacheTtl
Cause: cacheTtl <60 seconds
Solution: Use minimum 60
typescript
// ❌ Error
await env.MY_KV.get('key', { cacheTtl: 30 });
// ✅ Correct
await env.MY_KV.get('key', { cacheTtl: 60 });原因: cacheTtl小于60秒
解决方案: 使用最小60秒的值
typescript
// ❌ 错误示例
await env.MY_KV.get('key', { cacheTtl: 30 });
// ✅ 正确示例
await env.MY_KV.get('key', { cacheTtl: 60 });Critical Rules
关键规则
Always Do ✅
必须遵循 ✅
- Use bulk operations when reading multiple keys (counts as 1 operation)
- Set cacheTtl for frequently-read, infrequently-updated data (min 60s)
- Store small values (<1024 bytes) in metadata when using frequently
list() - Check when paginating, not
list_completekeys.length === 0 - Use retry logic with exponential backoff for write operations
- Validate sizes before writing (key 512B, value 25MiB, metadata 1KB)
- Coalesce related keys for better caching performance
- Use KV for read-heavy workloads (100:1 read/write ratio ideal)
- 读取多个键时使用批量操作(计为1次操作)
- 对频繁读取、很少更新的数据设置cacheTtl(最小60秒)
- 当频繁使用时,将小值(<1024字节)存储在元数据中
list() - 分页时检查,而非
list_completekeys.length === 0 - 写入操作使用带指数退避的重试逻辑
- 写入前验证大小(键512B、值25MiB、元数据1KB)
- 合并相关键以提升缓存性能
- 将KV用于读密集型工作负载(理想读写比100:1)
Never Do ❌
严禁操作 ❌
- Never write to same key >1/second (causes 429 rate limit errors)
- Never assume immediate global consistency (takes ~60 seconds to propagate)
- Never use KV for atomic operations (use Durable Objects instead)
- Never set cacheTtl <60 seconds (will fail)
- Never commit namespace IDs to public repos (use environment variables)
- Never exceed 1000 operations per invocation (use bulk operations)
- Never rely on write order (eventual consistency = no guarantees)
- Never forget to handle null values (returns
get()if key doesn't exist)null
- 严禁对同一键每秒写入超过1次(会触发429速率限制错误)
- 严禁假设全局数据立即一致(传播需要约60秒)
- 严禁将KV用于原子操作(改用Durable Objects)
- 严禁设置cacheTtl小于60秒(会执行失败)
- 严禁将命名空间ID提交到公共仓库(使用环境变量)
- 严禁单次调用超过1000次操作(使用批量操作)
- 严禁依赖写入顺序(最终一致性无顺序保证)
- 严禁忘记处理null值(在键不存在时返回
get())null
Troubleshooting
故障排查
Issue 1: "429 Too Many Requests" on writes
问题1:写入时出现“429 Too Many Requests”
Cause: Writing to same key >1/second
Solution: Consolidate writes or use retry with exponential backoff
typescript
// ❌ Bad: Rate limit
for (let i = 0; i < 10; i++) await kv.put('counter', String(i));
// ✅ Good: Single write
await kv.put('counter', '9');
// ✅ Good: Retry with backoff
await putWithRetry(kv, 'counter', String(i));原因: 对同一键的写入超过每秒1次
解决方案: 合并写入操作或使用带指数退避的重试机制
typescript
// ❌ 错误示例:触发速率限制
for (let i = 0; i < 10; i++) await kv.put('counter', String(i));
// ✅ 正确示例:单次写入
await kv.put('counter', '9');
// ✅ 正确示例:带退避的重试
await putWithRetry(kv, 'counter', String(i));Issue 2: Stale reads after write
问题2:写入后读取到旧数据
Cause: Eventual consistency (~60 seconds propagation)
Solution: Accept stale reads, use Durable Objects for strong consistency, or implement app-level cache invalidation
原因: 最终一致性(传播需要约60秒)
解决方案: 接受旧数据、改用Durable Objects实现强一致性,或在应用层实现缓存失效
Issue 3: "Operations limit exceeded"
问题3:“Operations limit exceeded”
Cause: >1000 KV operations in single Worker invocation
Solution: Use bulk operations
typescript
// ❌ Bad: 5000 operations
for (const key of 5000keys) await kv.get(key);
// ✅ Good: 1 operation
const values = await kv.get(keys); // Bulk read原因: 单次Worker调用中KV操作超过1000次
解决方案: 使用批量操作
typescript
// ❌ 错误示例:5000次操作
for (const key of 5000keys) await kv.get(key);
// ✅ 正确示例:1次操作
const values = await kv.get(keys); // 批量读取Issue 4: List returns empty but cursor exists
问题4:List返回空但cursor存在
Cause: Deleted/expired keys create "tombstones" that must be iterated through
Solution: Always check , not
list_completekeys.lengthtypescript
// ✅ Correct pagination
let cursor: string | undefined;
do {
const result = await kv.list({ cursor });
processKeys(result.keys); // Even if empty
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);CRITICAL: When using , you must include it in all paginated calls:
prefixtypescript
// ❌ WRONG - Loses prefix on subsequent pages
let result = await kv.list({ prefix: 'user:' });
result = await kv.list({ cursor: result.cursor }); // Missing prefix!
// ✅ CORRECT - Include prefix on every call
let cursor: string | undefined;
do {
const result = await kv.list({ prefix: 'user:', cursor });
processKeys(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);Source: List keys documentation
原因: 删除/过期的键会生成“墓碑”,需要迭代处理
解决方案: 始终检查,而非
list_completekeys.lengthtypescript
// ✅ 正确分页方式
let cursor: string | undefined;
do {
const result = await kv.list({ cursor });
processKeys(result.keys); // 即使为空也要处理
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);重要提示: 使用时,必须在所有分页调用中包含它:
prefixtypescript
// ❌ 错误示例 - 后续页面丢失prefix
let result = await kv.list({ prefix: 'user:' });
result = await kv.list({ cursor: result.cursor }); // 缺少prefix!
// ✅ 正确示例 - 每次调用都包含prefix
let cursor: string | undefined;
do {
const result = await kv.list({ prefix: 'user:', cursor });
processKeys(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);Issue 5: wrangler types
Does Not Generate Types for Environment-Nested KV Bindings
wrangler types问题5:wrangler types
未为环境嵌套的KV绑定生成类型
wrangler typesCause: KV namespaces defined within environment configurations (e.g., ) are not included in generated TypeScript types
Impact: Loss of TypeScript autocomplete and type checking for KV bindings
Source: GitHub Issue #9709
[env.feature.kv_namespaces]Example Configuration:
toml
undefined原因: 在环境配置中定义的KV命名空间(例如)不会被包含在生成的TypeScript类型中
影响: 丢失KV绑定的TypeScript自动补全和类型检查
来源: GitHub Issue #9709
[env.feature.kv_namespaces]示例配置:
toml
undefinedwrangler.toml
wrangler.toml
[env.feature]
name = "my-worker-feature"
[[env.feature.kv_namespaces]]
binding = "MY_STORAGE_FEATURE"
id = "xxxxxxxxxxxx"
Running `npx wrangler types` creates type definitions for environment variables but not for the KV namespace bindings.
**Workaround:**
```bash[env.feature]
name = "my-worker-feature"
[[env.feature.kv_namespaces]]
binding = "MY_STORAGE_FEATURE"
id = "xxxxxxxxxxxx"
运行`npx wrangler types`会为环境变量生成类型定义,但不会包含KV命名空间绑定。
**解决方法:**
```bashGenerate types for specific environment
为特定环境生成类型
npx wrangler types -e feature
Or define KV namespaces at top level instead of nested in environments:
```tomlnpx wrangler types -e feature
或者将KV命名空间定义在顶层而非环境嵌套中:
```tomlTop-level (types generated correctly)
顶层定义(类型生成正常)
[[kv_namespaces]]
binding = "MY_STORAGE"
id = "xxxxxxxxxxxx"
**Note**: Runtime bindings still work correctly; this only affects type generation.[[kv_namespaces]]
binding = "MY_STORAGE"
id = "xxxxxxxxxxxx"
**注意**: 运行时绑定仍可正常工作;此问题仅影响类型生成。Issue 6: wrangler kv key list
Returns Empty Array for Remote Data
wrangler kv key list问题6:wrangler kv key list
对远程数据返回空数组
wrangler kv key listCause: CLI commands default to local storage, not remote/production KV
Impact: Users expect to see production data but get empty array from local storage
Source: GitHub Issue #10395
Solution: Use flag to access production/remote data
--remotebash
undefined原因: CLI命令默认使用本地存储,而非远程/生产KV
影响: 用户期望查看生产数据,但获取到本地存储的空数组
来源: GitHub Issue #10395
解决方案: 使用标志访问远程/生产数据
--remotebash
undefined❌ Shows local storage (likely empty)
❌ 显示本地存储(通常为空)
npx wrangler kv key list --binding=MY_KV
npx wrangler kv key list --binding=MY_KV
✅ Shows remote/production data
✅ 显示远程/生产数据
npx wrangler kv key list --binding=MY_KV --remote
**Why This Happens**: By design, `wrangler dev` uses local KV storage to avoid interfering with production data. CLI commands follow the same default for consistency.
**Applies to**: All `wrangler kv key` commands (get, list, delete, put)
---npx wrangler kv key list --binding=MY_KV --remote
**原因**: 设计如此,`wrangler dev`使用本地KV存储以避免影响生产数据。CLI命令为保持一致性采用相同默认行为。
**适用范围**: 所有`wrangler kv key`命令(get、list、delete、put)
---Production Checklist
生产环境检查清单
- Environment-specific namespaces configured (vs
id)preview_id - Namespace IDs stored in environment variables (not hardcoded)
- Rate limit retry logic implemented for writes
- Appropriate values set for reads (min 60s)
cacheTtl - Sizes validated (key 512B, value 25MiB, metadata 1KB)
- Bulk operations used where possible
- Pagination with check (not
list_complete)keys.length - Error handling for null values
- Monitoring/alerting for rate limits
- 配置了环境特定的命名空间(vs
id)preview_id - 命名空间ID存储在环境变量中(未硬编码)
- 为写入操作实现了速率限制重试逻辑
- 为读取操作设置了合适的值(最小60秒)
cacheTtl - 写入前验证了大小(键512B、值25MiB、元数据1KB)
- 尽可能使用了批量操作
- 分页时检查(而非
list_complete)keys.length - 处理了null值的错误情况
- 配置了速率限制的监控/告警
Related Documentation
相关文档
Last Updated: 2026-01-20
Package Versions: wrangler@4.59.2, @cloudflare/workers-types@4.20260109.0
Changes: Added 6 research findings - hot/cold key performance patterns, remote bindings (Wrangler 4.37+), wrangler types environment issue, CLI --remote flag requirement, RYOW consistency details, prefix persistence in pagination
最后更新: 2026-01-20
包版本: wrangler@4.59.2, @cloudflare/workers-types@4.20260109.0
变更: 添加6项研究成果 - 热/冷键性能模式、远程绑定(Wrangler 4.37+)、wrangler types环境问题、CLI --remote标志要求、RYOW一致性细节、分页时的prefix持久化