cloudflare-kv

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare 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
undefined
bash
undefined

Create 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 metadata
typescript
// 获取单个键
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.json

typescript
// 删除单个键
await env.MY_KV.delete('key');  // 始终执行成功

// 批量删除(仅支持CLI,最多10000个键)
// npx wrangler kv bulk delete --binding=MY_KV keys.json

Advanced 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:
TypeResponse TimeWhen It Happens
Hot keys6-8msRead 2+ times/minute per datacenter
Cold keys100-300msInfrequently 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,
cacheTtl
reduces cold read latency.
Trade-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:
  1. Writes immediately visible in same location (read-your-own-write consistency within same POP)
  2. Other locations see update within ~60 seconds (or cacheTtl value)
  3. 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

KV在Cloudflare全球网络中是最终一致的(2025年8月重构:混合存储,p99延迟<5ms):
工作原理:
  1. 写入操作在同一位置立即可见(同一POP内的读己写一致性)
  2. 其他位置会在约60秒内看到更新(或根据cacheTtl值)
  3. 缓存读取在传播期间可能返回旧数据
示例:
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
undefined
bash
undefined

Create 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
remote: true
instead of production.
Version Support:
  • Wrangler 4.37.0+
  • @cloudflare/vite-plugin 1.13.0+
  • @cloudflare/vitest-pool-workers 0.9.0+

开发期间将本地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

限制与配额

FeatureFree PlanPaid Plan
Reads per day100,000Unlimited
Writes per day (different keys)1,000Unlimited
Writes per key per second11
Operations per Worker invocation1,0001,000
Namespaces per account1,0001,000
Storage per account1 GBUnlimited
Key size512 bytes512 bytes
Metadata size1024 bytes1024 bytes
Value size25 MiB25 MiB
Minimum cacheTtl60 seconds60 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无限制
每个键每秒写入次数11
单次Worker调用的操作次数1,0001,000
每个账户的命名空间数量1,0001,000
每个账户的存储容量1 GB无限制
键大小512字节512字节
元数据大小1024字节1024字节
值大小25 MiB25 MiB
最小cacheTtl60秒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 ✅

必须遵循 ✅

  1. Use bulk operations when reading multiple keys (counts as 1 operation)
  2. Set cacheTtl for frequently-read, infrequently-updated data (min 60s)
  3. Store small values (<1024 bytes) in metadata when using
    list()
    frequently
  4. Check
    list_complete
    when paginating, not
    keys.length === 0
  5. Use retry logic with exponential backoff for write operations
  6. Validate sizes before writing (key 512B, value 25MiB, metadata 1KB)
  7. Coalesce related keys for better caching performance
  8. Use KV for read-heavy workloads (100:1 read/write ratio ideal)
  1. 读取多个键时使用批量操作(计为1次操作)
  2. 对频繁读取、很少更新的数据设置cacheTtl(最小60秒)
  3. 当频繁使用
    list()
    时,将小值(<1024字节)存储在元数据中
  4. 分页时检查
    list_complete
    ,而非
    keys.length === 0
  5. 写入操作使用带指数退避的重试逻辑
  6. 写入前验证大小(键512B、值25MiB、元数据1KB)
  7. 合并相关键以提升缓存性能
  8. 将KV用于读密集型工作负载(理想读写比100:1)

Never Do ❌

严禁操作 ❌

  1. Never write to same key >1/second (causes 429 rate limit errors)
  2. Never assume immediate global consistency (takes ~60 seconds to propagate)
  3. Never use KV for atomic operations (use Durable Objects instead)
  4. Never set cacheTtl <60 seconds (will fail)
  5. Never commit namespace IDs to public repos (use environment variables)
  6. Never exceed 1000 operations per invocation (use bulk operations)
  7. Never rely on write order (eventual consistency = no guarantees)
  8. Never forget to handle null values (
    get()
    returns
    null
    if key doesn't exist)

  1. 严禁对同一键每秒写入超过1次(会触发429速率限制错误)
  2. 严禁假设全局数据立即一致(传播需要约60秒)
  3. 严禁将KV用于原子操作(改用Durable Objects)
  4. 严禁设置cacheTtl小于60秒(会执行失败)
  5. 严禁将命名空间ID提交到公共仓库(使用环境变量)
  6. 严禁单次调用超过1000次操作(使用批量操作)
  7. 严禁依赖写入顺序(最终一致性无顺序保证)
  8. 严禁忘记处理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
list_complete
, not
keys.length
typescript
// ✅ 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
prefix
, you must include it in all paginated calls:
typescript
// ❌ 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);
原因: 删除/过期的键会生成“墓碑”,需要迭代处理 解决方案: 始终检查
list_complete
,而非
keys.length
typescript
// ✅ 正确分页方式
let cursor: string | undefined;
do {
  const result = await kv.list({ cursor });
  processKeys(result.keys);  // 即使为空也要处理
  cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
重要提示: 使用
prefix
时,必须在所有分页调用中包含它:
typescript
// ❌ 错误示例 - 后续页面丢失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

问题5:
wrangler types
未为环境嵌套的KV绑定生成类型

Cause: KV namespaces defined within environment configurations (e.g.,
[env.feature.kv_namespaces]
) are not included in generated TypeScript types Impact: Loss of TypeScript autocomplete and type checking for KV bindings Source: GitHub Issue #9709
Example Configuration:
toml
undefined
原因: 在环境配置中定义的KV命名空间(例如
[env.feature.kv_namespaces]
)不会被包含在生成的TypeScript类型中 影响: 丢失KV绑定的TypeScript自动补全和类型检查 来源: GitHub Issue #9709
示例配置:
toml
undefined

wrangler.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命名空间绑定。

**解决方法:**
```bash

Generate types for specific environment

为特定环境生成类型

npx wrangler types -e feature

Or define KV namespaces at top level instead of nested in environments:
```toml
npx wrangler types -e feature

或者将KV命名空间定义在顶层而非环境嵌套中:
```toml

Top-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

问题6:
wrangler kv key list
对远程数据返回空数组

Cause: 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
--remote
flag to access production/remote data
bash
undefined
原因: CLI命令默认使用本地存储,而非远程/生产KV 影响: 用户期望查看生产数据,但获取到本地存储的空数组 来源: GitHub Issue #10395
解决方案: 使用
--remote
标志访问远程/生产数据
bash
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 (
    id
    vs
    preview_id
    )
  • Namespace IDs stored in environment variables (not hardcoded)
  • Rate limit retry logic implemented for writes
  • Appropriate
    cacheTtl
    values set for reads (min 60s)
  • Sizes validated (key 512B, value 25MiB, metadata 1KB)
  • Bulk operations used where possible
  • Pagination with
    list_complete
    check (not
    keys.length
    )
  • Error handling for null values
  • Monitoring/alerting for rate limits

  • 配置了环境特定的命名空间(
    id
    vs
    preview_id
  • 命名空间ID存储在环境变量中(未硬编码)
  • 为写入操作实现了速率限制重试逻辑
  • 为读取操作设置了合适的
    cacheTtl
    值(最小60秒)
  • 写入前验证了大小(键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持久化