关于 cloudflare-kv
cloudflare-kv 技能为 Cloudflare Workers KV 的配置、读写和运维提供 TypeScript 与 Wrangler 指南。适合构建读多写少、可接受全球数据最终一致性的 Worker 应用。
主要功能
- 01指导命名空间创建与绑定配置
- 02说明读取、写入、列表和删除操作
- 03涵盖批量操作与分页模式
- 04介绍缓存、元数据和键合并实践
- 05说明一致性特点、限制与常见错误
适用场景
- 01在 Worker 中存储配置或用户偏好
- 02使用 KV 实现缓存或功能开关
- 03通过 Wrangler 管理键值数据
- 04为读取密集型应用规划 KV 存储
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill cloudflare-kv安装后在 Claude Code 中输入/cloudflare-kv确认已加载。
附带文件README.mdSKILL.json
详细介绍
Cloudflare Workers KV
状态:已可用于生产环境 ✅ 最近更新:2025-10-21 依赖项:cloudflare-worker-base(用于 Worker 设置) 最新版本:[email protected],@cloudflare/[email protected]
快速入门(5 分钟)
1. 创建 KV 命名空间
# Create a new KV namespace
npx wrangler kv namespace create MY_NAMESPACE
# Output includes namespace_id - save this!
# ✅ Success!
# Add the following to your wrangler.toml or wrangler.jsonc:
#
# [[kv_namespaces]]
# binding = "MY_NAMESPACE"
# id = "<UUID>"
用于开发的(预览)命名空间:
npx wrangler kv namespace create MY_NAMESPACE --preview
# Output:
# [[kv_namespaces]]
# binding = "MY_NAMESPACE"
# preview_id = "<UUID>"
2. 配置绑定
添加到你的 wrangler.jsonc 中:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-11",
"kv_namespaces": [
{
"binding": "MY_NAMESPACE", // Available as env.MY_NAMESPACE
"id": "<production-uuid>", // Production namespace ID
"preview_id": "<preview-uuid>" // Local dev namespace ID (optional)
}
]
}
或者使用 wrangler.toml:
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-10-11"
[[kv_namespaces]]
binding = "MY_NAMESPACE"
id = "<production-uuid>"
preview_id = "<preview-uuid>" # optional
重要:
binding是你在代码中访问命名空间的方式(env.MY_NAMESPACE)id是生产环境命名空间的 UUIDpreview_id用于本地开发(可选,使用单独的命名空间)- 切勿将真实的命名空间 ID 提交到公开仓库——请使用环境变量或机密
3. 写入你的第一个键值对
import { Hono } from 'hono';
type Bindings = {
MY_NAMESPACE: KVNamespace;
};
const app = new Hono<{ Bindings: Bindings }>();
app.post('/set/:key', async (c) => {
const key = c.req.param('key');
const value = await c.req.text();
// Simple write
await c.env.MY_NAMESPACE.put(key, value);
return c.json({ success: true, key });
});
app.get('/get/:key', async (c) => {
const key = c.req.param('key');
const value = await c.env.MY_NAMESPACE.get(key);
if (!value) {
return c.json({ error: 'Not found' }, 404);
}
return c.json({ value });
});
export default app;
4. 在本地测试
# Start local development server
npm run dev
# In another terminal, test the endpoints
curl -X POST http://localhost:8787/set/test -d "Hello KV"
# {"success":true,"key":"test"}
curl http://localhost:8787/get/test
# {"value":"Hello KV"}
Workers KV 完整 API
1. 读取操作
get() - 读取单个键
// Get as string (default)
const value: string | null = await env.MY_KV.get('my-key');
// Get as JSON
const data: MyType | null = await env.MY_KV.get('my-key', { type: 'json' });
// Get as ArrayBuffer
const buffer: ArrayBuffer | null = await env.MY_KV.get('my-key', { type: 'arrayBuffer' });
// Get as ReadableStream
const stream: ReadableStream | null = await env.MY_KV.get('my-key', { type: 'stream' });
// Get with cache optimization
const value = await env.MY_KV.get('my-key', {
type: 'text',
cacheTtl: 300, // Cache at edge for 5 minutes (minimum 60 seconds)
});
get() - 读取多个键(批量)
// Read multiple keys at once (counts as 1 operation)
const keys = ['key1', 'key2', 'key3'];
const values: Map<string, string | null> = await env.MY_KV.get(keys);
// Access values
const value1 = values.get('key1'); // string | null
const value2 = values.get('key2'); // string | null
// Convert to object
const obj = Object.fromEntries(values);
getWithMetadata() - 读取值和元数据
// Get single key with metadata
const { value, metadata } = await env.MY_KV.getWithMetadata('my-key');
// value: string | null
// metadata: any | null
// Get as JSON with metadata
const { value, metadata } = await env.MY_KV.getWithMetadata<MyType>('my-key', {
type: 'json',
cacheTtl: 300,
});
// Get multiple keys with metadata
const keys = ['key1', 'key2'];
const result: Map<string, { value: string | null, metadata: any | null }> =
await env.MY_KV.getWithMetadata(keys);
for (const [key, data] of result) {
console.log(key, data.value, data.metadata);
}
类型选项:
text(默认)- 返回stringjson- 解析 JSON,返回objectarrayBuffer- 返回ArrayBufferstream- 返回ReadableStream
注意: 使用 get(keys[]) 批量读取时,仅支持 text 和 json 类型。对于 arrayBuffer 或 stream,请使用 Promise.all() 调用多个单独的 get()。
2. 写入操作
put() - 写入键值对
// Simple write
await env.MY_KV.put('key', 'value');
// Write JSON
await env.MY_KV.put('user:123', JSON.stringify({ name: 'John', age: 30 }));
// Write with expiration (TTL)
await env.MY_KV.put('session:abc', sessionData, {
expirationTtl: 3600, // Expire in 1 hour (minimum 60 seconds)
});
// Write with absolute expiration
const expirationTime = Math.floor(Date.now() / 1000) + 86400; // 24 hours from now
await env.MY_KV.put('token', tokenValue, {
expiration: expirationTime, // Seconds since epoch
});
// Write with metadata
await env.MY_KV.put('config:theme', 'dark', {
metadata: {
updatedAt: Date.now(),
updatedBy: 'admin',
version: 2
},
});
// Write with everything
await env.MY_KV.put('feature:flags', JSON.stringify(flags), {
expirationTtl: 600,
metadata: { source: 'api', timestamp: Date.now() },
});
重要限制:
- 键大小:最大 512 字节
- 值大小:最大 25 MiB
- 元数据大小:最大 1024 字节(JSON 序列化后)
- 写入速率:每个键每秒最多写入 1 次
- 过期时间下限:60 秒(TTL 和绝对过期时间均适用)
处理速率限制:
async function putWithRetry(
kv: KVNamespace,
key: string,
value: string,
options?: KVPutOptions
) {
let attempts = 0;
const maxAttempts = 5;
let delay = 1000; // Start with 1 second
while (attempts < maxAttempts) {
try {
await kv.put(key, value, options);
return; // Success
} catch (error) {
const message = (error as Error).message;
if (message.includes('429') || message.includes('Too Many Requests')) {
attempts++;
if (attempts >= maxAttempts) {
throw new Error('Max retry attempts reached');
}
console.warn(`Attempt ${attempts} failed. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
// Exponential backoff
delay *= 2;
} else {
throw error; // Different error, rethrow
}
}
}
}
3. 列表操作
list() - 列出键
// List all keys (up to 1000)
const result = await env.MY_KV.list();
console.log(result.keys); // Array of key objects
console.log(result.list_complete); // boolean - false if more keys exist
console.log(result.cursor); // string - for pagination
// List with prefix filter
const result = await env.MY_KV.list({
prefix: 'user:', // Only keys starting with 'user:'
});
// List with limit
const result = await env.MY_KV.list({
limit: 100, // Maximum 1000 (default 1000)
});
// Pagination with cursor
let cursor: string | undefined;
let allKeys: any[] = [];
do {
const result = await env.MY_KV.list({ cursor });
allKeys = allKeys.concat(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
// Combined: prefix + pagination
let cursor: string | undefined;
const userKeys: any[] = [];
do {
const result = await env.MY_KV.list({
prefix: 'user:',
cursor,
});
userKeys.push(...result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
列表响应格式:
{
keys: [
{
name: "user:123",
expiration: 1234567890, // Optional: seconds since epoch
metadata: { ... } // Optional: metadata object
},
// ... more keys
],
list_complete: false, // true if no more keys
cursor: "6Ck1la0VxJ0djhidm1MdX2FyD" // Use for next page
}
重要:
- 键始终按字典序(UTF-8)返回
- 始终检查
list_complete,不要检查keys.length === 0 keys数组为空并不意味着没有更多数据(过期或已删除的键会产生“墓碑”)- 使用
prefix分页时,必须在每次携带cursor的请求中传入相同的prefix
4. 删除操作
delete() - 删除键
// Delete single key
await env.MY_KV.delete('my-key');
// Delete always succeeds, even if key doesn't exist
await env.MY_KV.delete('non-existent-key'); // No error
// Bulk delete pattern (in Worker)
const keysToDelete = ['key1', 'key2', 'key3', ...];
// Delete in parallel (careful of Worker subrequest limits)
await Promise.all(
keysToDelete.map(key => env.MY_KV.delete(key))
);
// For more than 1000 keys, use REST API bulk delete (via wrangler or API)
通过 REST API 批量删除:
Workers 绑定不支持批量删除,但你可以使用 REST API(通过 wrangler 或直接调用 API):
# Using wrangler CLI
npx wrangler kv bulk delete --namespace-id=<UUID> keys.json
# keys.json format:
# ["key1", "key2", "key3"]
**REST API 限制:**每次批量删除请求最多可处理 10,000 个键。
高级模式与最佳实践
1. 使用 CacheTtl 的缓存模式
async function getCachedData(
kv: KVNamespace,
cacheKey: string,
fetchFn: () => Promise<any>,
cacheTtl: number = 300
) {
// Try to get from KV cache
const cached = await kv.get(cacheKey, {
type: 'json',
cacheTtl, // Cache at edge for faster subsequent reads
});
if (cached) {
return cached;
}
// Cache miss - fetch fresh data
const data = await fetchFn();
// Store in KV with expiration
await kv.put(cacheKey, JSON.stringify(data), {
expirationTtl: cacheTtl * 2, // Store longer than cache
});
return data;
}
// Usage
app.get('/api/data/:id', async (c) => {
const id = c.req.param('id');
const data = await getCachedData(
c.env.CACHE,
`data:${id}`,
() => fetchFromDatabase(id),
300 // 5 minutes
);
return c.json(data);
});
CacheTtl 指南:
- **最小值:**60 秒
- **默认值:**60 秒
- 最大值:
Number.MAX_SAFE_INTEGER - **适用场景:**频繁读取、很少更新的数据
- **权衡:**CacheTtl 越高,读取越快,但更新传播越慢
2. 元数据优化模式
将较小的值存储在元数据中,避免单独调用 get():
// ❌ Bad: Two operations
await env.MY_KV.put('user:123', 'active');
const status = await env.MY_KV.get('user:123');
// ✅ Good: Store in metadata with empty value
await env.MY_KV.put('user:123', '', {
metadata: {
status: 'active',
lastSeen: Date.now(),
plan: 'pro'
},
});
// List returns metadata automatically
const users = await env.MY_KV.list({ prefix: 'user:' });
users.keys.forEach(({ name, metadata }) => {
console.log(name, metadata.status, metadata.plan);
// No additional get() calls needed!
});
适用场景:
- ✅ 值的大小不超过 1024 字节
- ✅ 你经常使用
list()操作 - ✅ 你需要筛选或处理大量键
- ❌ 不适用于较大的值(请使用常规值存储)
3. 合并键以提升性能
将相关的冷键与热键合并:
// ❌ Bad: Many individual keys (some hot, some cold)
await kv.put('user:123:name', 'John');
await kv.put('user:123:email', '[email protected]');
await kv.put('user:123:age', '30');
// ✅ Good: Coalesce into single hot key
await kv.put('user:123', JSON.stringify({
name: 'John',
email: '[email protected]',
age: 30,
}));
// Single read gets everything
const user = await kv.get<User>('user:123', { type: 'json' });
优点:
- 冷键可受益于热键缓存
- 操作次数更少,性能更好
- 使用单个缓存条目,而非多个
缺点:
- 难以单独更新字段(需要先读取、修改再写入)
- 合并后的值过大时可能会触及内存限制
- 并发更新需要锁定机制
4. 分页辅助函数
async function* paginateKV(
kv: KVNamespace,
options: { prefix?: string; limit?: number } = {}
) {
let cursor: string | undefined;
do {
const result = await kv.list({
prefix: options.prefix,
limit: options.limit || 1000,
cursor,
});
yield result.keys;
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
}
// Usage
app.get('/all-users', async (c) => {
const allUsers = [];
for await (const keys of paginateKV(c.env.MY_KV, { prefix: 'user:' })) {
// Process batch
allUsers.push(...keys.map(k => k.name));
}
return c.json({ users: allUsers, count: allUsers.length });
});
5. 功能标志模式
interface FeatureFlags {
darkMode: boolean;
newDashboard: boolean;
betaFeatures: boolean;
}
async function getFeatureFlags(
kv: KVNamespace,
userId?: string
): Promise<FeatureFlags> {
// Try user-specific flags first
if (userId) {
const userFlags = await kv.get<FeatureFlags>(`flags:user:${userId}`, {
type: 'json',
cacheTtl: 300,
});
if (userFlags) return userFlags;
}
// Fallback to global flags
const globalFlags = await kv.get<FeatureFlags>('flags:global', {
type: 'json',
cacheTtl: 300,
});
return globalFlags || {
darkMode: false,
newDashboard: false,
betaFeatures: false,
};
}
// Update global flags
app.post('/admin/flags', async (c) => {
const flags = await c.req.json<FeatureFlags>();
await c.env.CONFIG.put('flags:global', JSON.stringify(flags), {
metadata: { updatedAt: Date.now() },
});
return c.json({ success: true });
});
理解最终一致性
KV 在 Cloudflare 的全球网络中采用最终一致性:
工作原理:
- 写入会立即在同一位置可见
- 其他位置会在 ~60 秒内(或你的
cacheTtl值所指定的时间内)看到更新 - 传播期间,缓存读取可能会返回过期数据
影响:
// In Tokyo data center:
await env.MY_KV.put('counter', '1');
const value1 = await env.MY_KV.get('counter'); // "1" ✅
// In London data center (within 60 seconds):
const value2 = await env.MY_KV.get('counter'); // Might still be old value ⚠️
// After 60+ seconds:
const value3 = await env.MY_KV.get('counter'); // "1" ✅
最佳实践:
✅ 适合使用 KV 的场景:
- 读密集型工作负载(读写比为 100:1)
- 无需立即实现全局一致性的数据
- 配置、功能开关、缓存
- 用户偏好设置、会话数据
❌ 不适合使用 KV 的场景:
- 需要原子操作的金融交易
- 需要强一致性的数据
- 对同一个键进行高频写入(>1 次/秒)
- 无法接受读取过时数据的关键数据
如果需要强一致性,请使用 Durable Objects。
Wrangler CLI 操作
创建命名空间
# Production namespace
npx wrangler kv namespace create MY_NAMESPACE
# Preview/development namespace
npx wrangler kv namespace create MY_NAMESPACE --preview
列出命名空间
npx wrangler kv namespace list
写入键值对
# Write single key
npx wrangler kv key put --binding=MY_NAMESPACE "my-key" "my-value"
# Write from file
npx wrangler kv key put --binding=MY_NAMESPACE "config" --path=config.json
# Write with metadata
npx wrangler kv key put --binding=MY_NAMESPACE "key" "value" --metadata='{"version":1}'
# Write with TTL (seconds)
npx wrangler kv key put --binding=MY_NAMESPACE "session" "data" --ttl=3600
读取键值对
# Read single key
npx wrangler kv key get --binding=MY_NAMESPACE "my-key"
# Read to file
npx wrangler kv key get --binding=MY_NAMESPACE "image" --path=image.png
列出键
# List all keys
npx wrangler kv key list --binding=MY_NAMESPACE
# List with prefix
npx wrangler kv key list --binding=MY_NAMESPACE --prefix="user:"
# Pretty print
npx wrangler kv key list --binding=MY_NAMESPACE | jq "."
删除键
# Delete single key
npx wrangler kv key delete --binding=MY_NAMESPACE "my-key"
批量操作
# Bulk write (up to 10,000 keys)
npx wrangler kv bulk put --binding=MY_NAMESPACE data.json
# data.json format:
# [
# {"key": "key1", "value": "value1"},
# {"key": "key2", "value": "value2", "expiration_ttl": 3600}
# ]
# Bulk delete (up to 10,000 keys)
npx wrangler kv bulk delete --binding=MY_NAMESPACE keys.json
# keys.json format:
# ["key1", "key2", "key3"]
限制和配额
| 功能 | 免费计划 | 付费计划 |
|---|---|---|
| 每日读取次数 | 100,000 | 无限制 |
| 每日写入次数(不同的键) | 1,000 | 无限制 |
| 每个键每秒写入次数 | 1 | 1 |
| 每次 Worker 调用的操作数 | 1,000 | 1,000 |
| 每个账户的命名空间数 | 1,000 | 1,000 |
| 每个账户的存储空间 | 1 GB | 无限制 |
| 每个命名空间的存储空间 | 1 GB | 无限制 |
| 每个命名空间的键数 | 无限制 | 无限制 |
| 键大小 | 512 bytes | 512 bytes |
| 元数据大小 | 1024 bytes | 1024 bytes |
| 值大小 | 25 MiB | 25 MiB |
| 最小 cacheTtl | 60 秒 | 60 秒 |
| 最大 cacheTtl | Number.MAX_SAFE_INTEGER | Number.MAX_SAFE_INTEGER |
重要说明:
- 每个键每秒 1 次写入:对同一个键进行并发写入会导致 429 错误
- 每次调用最多 1000 个操作:批量操作计为 1 个操作
- 批量读取(读取多个键)计为单个操作
- REST API 受 Cloudflare API 速率限制约束
TypeScript 类型
// KVNamespace type is provided by @cloudflare/workers-types
interface KVNamespace {
get(key: string, options?: Partial<KVGetOptions<undefined>>): Promise<string | null>;
get(key: string, type: "text"): Promise<string | null>;
get<ExpectedValue = unknown>(key: string, type: "json"): Promise<ExpectedValue | null>;
get(key: string, type: "arrayBuffer"): Promise<ArrayBuffer | null>;
get(key: string, type: "stream"): Promise<ReadableStream | null>;
get(key: string, options?: KVGetOptions<"text">): Promise<string | null>;
get<ExpectedValue = unknown>(key: string, options?: KVGetOptions<"json">): Promise<ExpectedValue | null>;
get(key: string, options?: KVGetOptions<"arrayBuffer">): Promise<ArrayBuffer | null>;
get(key: string, options?: KVGetOptions<"stream">): Promise<ReadableStream | null>;
get(keys: string[]): Promise<Map<string, string | null>>;
get(keys: string[], type: "text"): Promise<Map<string, string | null>>;
get<ExpectedValue = unknown>(keys: string[], type: "json"): Promise<Map<string, ExpectedValue | null>>;
getWithMetadata<Metadata = unknown>(key: string, options?: Partial<KVGetOptions<undefined>>): Promise<KVGetWithMetadataResult<string, Metadata>>;
getWithMetadata<Metadata = unknown>(key: string, type: "text"): Promise<KVGetWithMetadataResult<string, Metadata>>;
getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: string, type: "json"): Promise<KVGetWithMetadataResult<ExpectedValue, Metadata>>;
getWithMetadata<Metadata = unknown>(key: string, options?: KVGetOptions<"text">): Promise<KVGetWithMetadataResult<string, Metadata>>;
getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: string, options?: KVGetOptions<"json">): Promise<KVGetWithMetadataResult<ExpectedValue, Metadata>>;
getWithMetadata<Metadata = unknown>(keys: string[]): Promise<Map<string, KVGetWithMetadataResult<string, Metadata>>>;
getWithMetadata<Metadata = unknown>(keys: string[], type: "text"): Promise<Map<string, KVGetWithMetadataResult<string, Metadata>>>;
getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(keys: string[], type: "json"): Promise<Map<string, KVGetWithMetadataResult<ExpectedValue, Metadata>>>;
put(key: string, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVPutOptions): Promise<void>;
delete(key: string): Promise<void>;
list<Metadata = unknown>(options?: KVListOptions): Promise<KVListResult<Metadata>>;
}
interface KVGetOptions<Type> {
type: Type;
cacheTtl?: number;
}
interface KVGetWithMetadataResult<Value, Metadata> {
value: Value | null;
metadata: Metadata | null;
}
interface KVPutOptions {
expiration?: number; // Seconds since epoch
expirationTtl?: number; // Seconds from now (minimum 60)
metadata?: any; // Serializable to JSON, max 1024 bytes
}
interface KVListOptions {
prefix?: string;
limit?: number; // Default 1000, max 1000
cursor?: string;
}
interface KVListResult<Metadata = unknown> {
keys: {
name: string;
expiration?: number;
metadata?: Metadata;
}[];
list_complete: boolean;
cursor?: string;
}
错误处理
常见错误
1. 速率限制(429 Too Many Requests)
try {
await env.MY_KV.put('counter', '1');
await env.MY_KV.put('counter', '2'); // Too fast! < 1 second
} catch (error) {
// Error: KV PUT failed: 429 Too Many Requests
console.error(error);
}
// Solution: Use retry with backoff (see putWithRetry example above)
2. 值过大
const largeValue = 'x'.repeat(26 * 1024 * 1024); // > 25 MiB
try {
await env.MY_KV.put('large', largeValue);
} catch (error) {
// Error: Value too large
console.error(error);
}
// Solution: Check size before writing
if (value.length > 25 * 1024 * 1024) {
throw new Error('Value exceeds 25 MiB limit');
}
3. 元数据过大
const metadata = { data: 'x'.repeat(2000) }; // > 1024 bytes serialized
try {
await env.MY_KV.put('key', 'value', { metadata });
} catch (error) {
// Error: Metadata too large
console.error(error);
}
// Solution: Validate metadata size
const serialized = JSON.stringify(metadata);
if (serialized.length > 1024) {
throw new Error('Metadata exceeds 1024 byte limit');
}
4. 无效的 CacheTtl
// ❌ Too low
await env.MY_KV.get('key', { cacheTtl: 30 }); // Error: minimum is 60
// ✅ Correct
await env.MY_KV.get('key', { cacheTtl: 60 });
务必做到 ✅
- 读取多个键时使用批量操作(计为 1 个操作)
- 为频繁读取、很少更新的数据设置 cacheTtl
- 频繁使用
list()时,将较小的值存储在元数据中 - 分页时检查
list_complete,而不是检查keys.length === 0 - 写入操作使用指数退避重试逻辑
- 写入前验证大小(键 512 bytes、值 25 MiB、元数据 1 KB)
- 本地开发时使用预览命名空间
- 设置恰当的 TTL 以使缓存失效(最短 60 秒)
- 合并相关键,以提升缓存性能
- 将 KV 用于读密集型工作负载(理想读写比为 100:1)
绝对不要做 ❌
- 绝不要以超过每秒 1 次的频率写入同一个键——这会导致 429 速率限制错误
- 绝不要假定全局一致性会立即生效——传播需要约 60 秒
- 绝不要将 KV 用于原子操作——请改用 Durable Objects
- 绝不要将 cacheTtl 设置为小于 60 秒——这会导致失败
- 绝不要将命名空间 ID 提交到公开代码仓库——请使用环境变量
- 绝不要在每次调用中超过 1000 个操作——请使用批量操作
- 绝不要依赖写入顺序——最终一致性意味着无法保证顺序
- 绝不要在未加密的情况下存储敏感数据——默认情况下,KV 静态存储的数据未经加密
- 绝不要将 KV 用于高频写入——它并非为写密集型工作负载而设计
- 绝不要忘记处理 null 值——键不存在时,
get()会返回null
故障排除
问题:“429 Too Many Requests” 写入错误
原因: 对同一个键每秒写入超过一次
解决方案:
// ❌ Bad
for (let i = 0; i < 10; i++) {
await kv.put('counter', String(i)); // Rate limit!
}
// ✅ Good - consolidate writes
const finalValue = '9';
await kv.put('counter', finalValue);
// ✅ Good - use retry with backoff
await putWithRetry(kv, 'counter', String(i));
问题:写入后读取到旧数据
原因: 最终一致性——写入最多需要 60 秒才能在全球范围内传播
解决方案:
// Accept that reads may be stale for up to 60 seconds
// OR use Durable Objects for strong consistency
// OR implement application-level cache invalidation
问题:“超出操作数量限制”
原因: 单次 Worker 调用中的 KV 操作超过 1000 次
解决方案:
// ❌ Bad - 5000 operations
const keys = Array.from({ length: 5000 }, (_, i) => `key${i}`);
for (const key of keys) {
await kv.get(key); // Exceeds 1000 limit
}
// ✅ Good - 1 operation (bulk read)
const values = await kv.get(keys);
问题:列表为空,但游标仍然存在
原因: 最近删除或过期的键会在列表中产生“墓碑”
解决方案:
// Always check list_complete, not keys.length
let cursor: string | undefined;
do {
const result = await kv.list({ cursor });
// Process keys even if empty
processKeys(result.keys);
// CORRECT: Check list_complete
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
生产环境检查清单
部署到生产环境之前:
- 已配置特定于环境的命名空间(
id与preview_id) - 命名空间 ID 存储在环境变量中(未硬编码)
- 已为写入操作实现速率限制重试逻辑
- 已为读取操作设置合适的
cacheTtl值 - 已验证元数据大小(<1024 字节)
- 已验证值的大小(<25 MiB)
- 已验证键的大小(<512 字节)
- 在可能的情况下使用批量操作
- 已正确为
list()实现分页 - 已处理空值错误
- 已为速率限制配置监控/告警
- 已记录最终一致性行为
相关文档
最后更新:2025-10-21 版本:1.0.0 维护者:Jeremy Dawes | [email protected]