mcpField
C
cloudflare-kv

cloudflare-kv

github.com/jackspace数据与数据库1640 次安装12 周前更新
代码仓库

关于 cloudflare-kv

cloudflare-kv 技能为 Cloudflare Workers KV 的配置、读写和运维提供 TypeScript 与 Wrangler 指南。适合构建读多写少、可接受全球数据最终一致性的 Worker 应用。

主要功能

  1. 01指导命名空间创建与绑定配置
  2. 02说明读取、写入、列表和删除操作
  3. 03涵盖批量操作与分页模式
  4. 04介绍缓存、元数据和键合并实践
  5. 05说明一致性特点、限制与常见错误

适用场景

  1. 01在 Worker 中存储配置或用户偏好
  2. 02使用 KV 实现缓存或功能开关
  3. 03通过 Wrangler 管理键值数据
  4. 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 是生产环境命名空间的 UUID
  • preview_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(默认)- 返回 string
  • json - 解析 JSON,返回 object
  • arrayBuffer - 返回 ArrayBuffer
  • stream - 返回 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 的全球网络中采用最终一致性:

工作原理:
  1. 写入会立即在同一位置可见
  2. 其他位置会在 ~60 秒内(或你的 cacheTtl 值所指定的时间内)看到更新
  3. 传播期间,缓存读取可能会返回过期数据
影响:
// 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无限制
每个键每秒写入次数11
每次 Worker 调用的操作数1,0001,000
每个账户的命名空间数1,0001,000
每个账户的存储空间1 GB无限制
每个命名空间的存储空间1 GB无限制
每个命名空间的键数无限制无限制
键大小512 bytes512 bytes
元数据大小1024 bytes1024 bytes
值大小25 MiB25 MiB
最小 cacheTtl60 秒60 秒
最大 cacheTtlNumber.MAX_SAFE_INTEGERNumber.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. 读取多个键时使用批量操作(计为 1 个操作)
  2. 为频繁读取、很少更新的数据设置 cacheTtl
  3. 频繁使用 list() 时,将较小的值存储在元数据中
  4. 分页时检查 list_complete,而不是检查 keys.length === 0
  5. 写入操作使用指数退避重试逻辑
  6. 写入前验证大小(键 512 bytes、值 25 MiB、元数据 1 KB)
  7. 本地开发时使用预览命名空间
  8. 设置恰当的 TTL 以使缓存失效(最短 60 秒)
  9. 合并相关键,以提升缓存性能
  10. 将 KV 用于读密集型工作负载(理想读写比为 100:1)

绝对不要做 ❌

  1. 绝不要以超过每秒 1 次的频率写入同一个键——这会导致 429 速率限制错误
  2. 绝不要假定全局一致性会立即生效——传播需要约 60 秒
  3. 绝不要将 KV 用于原子操作——请改用 Durable Objects
  4. 绝不要将 cacheTtl 设置为小于 60 秒——这会导致失败
  5. 绝不要将命名空间 ID 提交到公开代码仓库——请使用环境变量
  6. 绝不要在每次调用中超过 1000 个操作——请使用批量操作
  7. 绝不要依赖写入顺序——最终一致性意味着无法保证顺序
  8. 绝不要在未加密的情况下存储敏感数据——默认情况下,KV 静态存储的数据未经加密
  9. 绝不要将 KV 用于高频写入——它并非为写密集型工作负载而设计
  10. 绝不要忘记处理 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]

评论

支持 Markdown