mcpField
C
cloudflare-kv

cloudflare-kv

github.com/jezwebデータ・データベース1.1k355 回インストール3 週間前に更新
リポジトリ

cloudflare-kv について

Cloudflare Workers KVを使った開発向けのスキルです。キー操作、キャッシュ、ページネーション、一貫性の挙動、Wranglerコマンド、利用制限を扱います。

主な機能

  1. 01キーの読み書き・一覧・削除
  2. 02キャッシュとメタデータのパターン
  3. 03キー一覧のページネーション
  4. 04一貫性とレート制限の指針
  5. 05Wrangler CLIコマンド例

活用シーン

  1. 01頻繁に読むデータのキャッシュ
  2. 02設定やフィーチャーフラグの保存
  3. 03プレフィックスごとのキー一覧取得
  4. 04ローカル開発でリモート名前空間を利用

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-kv

インストール後、Claude Code で/cloudflare-kvを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
.claude-plugin/
plugin.json
references/
best-practices.md
workers-api.md
rules/
cloudflare-kv.md
templates/
kv-basic-operations.ts
kv-caching-pattern.ts
kv-list-pagination.ts
kv-metadata-pattern.ts

詳細

Cloudflare Workers KV

ステータス: 本番環境で利用可能 ✅ 最終更新日: 2026-01-20 依存関係: cloudflare-worker-base(Worker のセットアップ用) 最新バージョン: [email protected]、@cloudflare/[email protected]

最近の更新(2025):

  • 2025年8月: アーキテクチャを再設計(パフォーマンスが40倍向上、p99レイテンシーが<5ms、R2を使ったハイブリッドストレージ)
  • 2025年4月: 一括読み取り API(1回のリクエストで最大100個のキーを取得でき、1回の操作としてカウント)
  • 2025年1月: ネームスペースの上限を引き上げ(Free および Paid プランで、アカウントあたり200から1,000ネームスペースに増加)

クイックスタート(5分)

# Create namespace
npx wrangler kv namespace create MY_NAMESPACE
# Output: [[kv_namespaces]] binding = "MY_NAMESPACE" id = "<UUID>"

wrangler.jsonc:

{
  "kv_namespaces": [{
    "binding": "MY_NAMESPACE",  // Access as env.MY_NAMESPACE
    "id": "<production-uuid>",
    "preview_id": "<preview-uuid>"  // Optional: local dev
  }]
}

基本的な使い方:

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);
});

KV API リファレンス

読み取り操作
// 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
書き込み操作
// 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 }
});

重要な制限:

  • キー: 最大512バイト
  • 値: 最大25 MiB
  • メタデータ: 最大1024バイト
  • 書き込みレート: キーあたり1秒に1回(超過すると429エラー)
  • 有効期限: 最短60秒
一覧表示操作
// 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);
削除操作
// 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

高度なパターン

CacheTtl を使ったキャッシュパターン
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)に使用します。

メタデータの最適化
// 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));
ホットキーとコールドキーについて

KV のパフォーマンスは、キーの温度によって異なります。

種類応答時間発生条件
ホットキー6-8msデータセンターごとに1分あたり2回以上読み取られる
コールドキー100-300msアクセス頻度が低く、中央ストレージから取得される

2025年8月以降の改善点:

  • すべての KV Worker 呼び出しにおける P90: <12ms(従来は22ms)
  • ホットキーの読み取りが最大3倍高速化
  • すべての操作が最大20ms高速化

最適化: キーの統合を使うと、コールドキーもホットキーのキャッシュの恩恵を受けられます。

// ❌ Bad: Many cold keys (300ms each)
await kv.put('user:123:name', 'John');
await kv.put('user:123:email', '[email protected]');
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: '[email protected]',
  plan: 'pro'
}));

// Single read, cached as hot key: ~6-8ms
const user = JSON.parse(await kv.get('user:123'));

CacheTtl はコールドキーに有効です: 読み取り頻度の低いデータでは、cacheTtl によりコールド読み取りのレイテンシーが低下します。

トレードオフ: 更新時には、統合のために読み取り・変更・書き込みが必要です。

ページネーションヘルパー
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);
}
指数バックオフを使ったレート制限時の再試行
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;
    }
  }
}

結果整合性について

KV は、Cloudflare のグローバルネットワーク全体で結果整合性を備えています(2025年8月の再設計: ハイブリッドストレージ、p99レイテンシー<5ms)。

仕組み:

  1. 同じロケーション内では書き込みがすぐに反映されます(同じ POP 内での Read-Your-Own-Write 整合性)
  2. 他のロケーションには、約60秒以内(または cacheTtl の値)に更新が反映されます
  3. 伝播中のキャッシュ読み取りでは、古いデータが返される場合があります

例:

// 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)の保証: 2025年8月の再設計以降、同じ Cloudflare ポイント・オブ・プレゼンスを経由するリクエストでは、自身の書き込みがすぐに反映されます。異なる POP 間でのグローバルな整合性には、引き続き最大60秒かかります。

タイムスタンプによる軽減パターン(厳密な整合性が重要な場合):

// 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);

KV の用途: 読み取り頻度の高いワークロード(比率100:1)、設定、機能フラグ、キャッシュ、ユーザー設定 KV を使わない用途: 金融取引、強い整合性、キーあたり1秒に1回を超える書き込み、重要なデータ

強い整合性が必要ですか? Durable Objects を使用してください。

出典: Workers KV の再設計


Wrangler CLI の基本

# Create namespace
npx wrangler kv namespace create MY_NAMESPACE [--preview]

# Manage keys (add --remote flag to access production data)
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)
npx wrangler kv bulk put --binding=MY_KV data.json
npx wrangler kv bulk delete --binding=MY_KV keys.json

重要: CLI コマンドはデフォルトでローカルストレージを使用します。本番環境またはリモートのデータにアクセスするには、--remote フラグを追加してください。


開発環境と本番環境

ローカル開発でのリモートバインディング(Wrangler 4.37+)

開発中にローカルの Workers を本番環境の KV 名前空間に接続します。

wrangler.jsonc:

{
  "kv_namespaces": [{
    "binding": "MY_KV",
    "id": "production-uuid",
    "remote": true  // Connect to live KV
  }]
}

仕組み:

  • ローカルの Worker コードはローカルで実行されます(高速に反復できます)
  • KV 操作はプロキシを経由して本番環境の名前空間にルーティングされます
  • 手動でのデータ投入は不要です

メリット:

  • デプロイせずに実際の本番データを使ってテストできます
  • 本番データにアクセスしながら、ローカルでコードを高速に実行できます
  • フィードバックループが速くなります(デプロイとテストを繰り返す必要がありません)

⚠️ 警告:書き込みは本番データに影響します。本番環境の代わりに、remote: true を設定したステージング名前空間の使用を検討してください。

バージョンサポート:

  • Wrangler 4.37.0+
  • @cloudflare/vite-plugin 1.13.0+
  • @cloudflare/vitest-pool-workers 0.9.0+

ソース:リモートバインディングのアーキテクチャ


制限とクォータ

機能無料プラン有料プラン
1 日あたりの読み取り回数100,000無制限
1 日あたりの書き込み回数(異なるキー)1,000無制限
キーあたりの 1 秒間の書き込み回数11
Worker の呼び出しあたりの操作数1,0001,000
アカウントあたりの名前空間数1,0001,000
アカウントあたりのストレージ容量1 GB無制限
キーのサイズ512 bytes512 bytes
メタデータのサイズ1024 bytes1024 bytes
値のサイズ25 MiB25 MiB
cacheTtl の最小値60 seconds60 seconds

重要:キーあたりの書き込みは 1 秒間に 1 回です(超過すると 429)。一括操作は 1 操作としてカウントされます。名前空間の上限は 200 から 1,000 に引き上げられました(2025 年 1 月)。


エラー処理

1. レート制限(429 Too Many Requests)

原因:同じキーに 1 秒間に 1 回を超えて書き込んでいます
解決策:指数バックオフを使った再試行を使用します(高度なパターンを参照)

// ❌ 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');
2. 値が大きすぎる

原因:値が 25 MiB を超えています
解決策:書き込み前にサイズを検証します

if (value.length > 25 * 1024 * 1024) throw new Error('Value exceeds 25 MiB');
3. メタデータが大きすぎる

原因:シリアル化後のメタデータが 1024 bytes を超えています
解決策:シリアル化後のサイズを検証します

const serialized = JSON.stringify(metadata);
if (serialized.length > 1024) throw new Error('Metadata exceeds 1024 bytes');
4. 無効な CacheTtl

原因:cacheTtl が 60 秒未満です
解決策:最小値の 60 を使用します

// ❌ Error
await env.MY_KV.get('key', { cacheTtl: 30 });

// ✅ Correct
await env.MY_KV.get('key', { cacheTtl: 60 });

重要なルール

必ず行うこと ✅
  1. 複数のキーを読み取る場合は一括操作を使用します(1 操作としてカウントされます)
  2. 頻繁に読み取られ、更新頻度が低いデータには cacheTtl を設定します(最小 60 秒)
  3. list() を頻繁に使用する場合は、小さな値(<1024 bytes)をメタデータに保存します
  4. ページネーションでは keys.length === 0 ではなく、list_complete を確認します
  5. 書き込み操作には、指数バックオフを使った再試行ロジックを使用します
  6. 書き込み前にサイズを検証します(キー 512B、値 25MiB、メタデータ 1KB)
  7. キャッシュ性能を高めるため、関連するキーをまとめます
  8. KV は読み取りが多いワークロードに使用します(読み取りと書き込みの比率は 100:1 が理想です)
絶対に行わないこと ❌
  1. 同じキーに 1 秒間に 1 回を超えて書き込まないでください(429 レート制限エラーが発生します)
  2. グローバルに即時一貫性が保たれると想定しないでください(伝播には約 60 秒かかります)
  3. アトミック操作に KV を使用しないでください(代わりに Durable Objects を使用します)
  4. cacheTtl を 60 秒未満に設定しないでください(失敗します)
  5. 名前空間 ID を公開リポジトリにコミットしないでください(環境変数を使用します)
  6. 呼び出しあたりの操作数を 1,000 を超えないようにしてください(一括操作を使用します)
  7. 書き込み順序に依存しないでください(結果整合性のため、順序は保証されません)
  8. null 値の処理を忘れないでください(キーが存在しない場合、get() は null を返します)

トラブルシューティング

問題 1:書き込み時に「429 Too Many Requests」が発生する

原因:同じキーに 1 秒間に 1 回を超えて書き込んでいます
解決策:書き込みをまとめるか、指数バックオフを使った再試行を使用します

// ❌ 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));
問題 2:書き込み後に古い値が読み取られる

原因:結果整合性(伝播に約 60 秒かかります)
解決策:古い値の読み取りを許容するか、強い一貫性が必要な場合は Durable Objects を使用するか、アプリケーションレベルでキャッシュ無効化を実装します

問題 3:「Operations limit exceeded」が発生する

原因:1 回の Worker 呼び出しで KV 操作を 1,000 回以上行っています
解決策:一括操作を使用します

// ❌ Bad: 5000 operations
for (const key of 5000keys) await kv.get(key);

// ✅ Good: 1 operation
const values = await kv.get(keys);  // Bulk read
Issue 4: リストが空を返すが、カーソルが存在する

原因: 削除済み/期限切れのキーによって「トゥームストーン」が作成され、これらを反復処理する必要があります 解決策: keys.length ではなく、必ず list_complete を確認してください

// ✅ 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);

重要: prefix を使用する場合、ページネーションを行うすべての呼び出しに含める必要があります。

// ❌ 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);

出典: キーのリストに関するドキュメント

Issue 5: wrangler types では環境内にネストされた KV バインディングの型が生成されない

原因: 環境設定内(例: [env.feature.kv_namespaces])で定義された KV 名前空間は、生成される TypeScript の型に含まれません 影響: KV バインディングの TypeScript オートコンプリートと型チェックが失われます 出典: GitHub Issue #9709

設定例:

# wrangler.toml
[env.feature]
name = "my-worker-feature"
[[env.feature.kv_namespaces]]
binding = "MY_STORAGE_FEATURE"
id = "xxxxxxxxxxxx"

npx wrangler types を実行すると、環境変数の型定義は作成されますが、KV 名前空間のバインディングの型定義は作成されません。

回避策:

# Generate types for specific environment
npx wrangler types -e feature

または、KV 名前空間を環境内にネストせず、トップレベルに定義してください。

# Top-level (types generated correctly)
[[kv_namespaces]]
binding = "MY_STORAGE"
id = "xxxxxxxxxxxx"

注: ランタイムのバインディングは引き続き正常に動作します。この問題は型生成にのみ影響します。

Issue 6: wrangler kv key list がリモートデータに対して空の配列を返す

原因: CLI コマンドはリモート/本番環境の KV ではなく、ローカルストレージをデフォルトで使用します 影響: ユーザーは本番データが表示されることを期待しますが、ローカルストレージから空の配列が返されます 出典: GitHub Issue #10395

解決策: --remote フラグを使用して、本番/リモートデータにアクセスしてください

# ❌ Shows local storage (likely empty)
npx wrangler kv key list --binding=MY_KV

# ✅ Shows remote/production data
npx wrangler kv key list --binding=MY_KV --remote

この問題が起きる理由: 仕様上、wrangler dev は本番データへの干渉を避けるため、ローカル KV ストレージを使用します。一貫性を保つため、CLI コマンドも同じ設定をデフォルトで使用します。

対象: すべての wrangler kv key コマンド(get、list、delete、put)


本番環境向けチェックリスト

  • 環境ごとの名前空間を設定済み(id と preview_id)
  • 名前空間 ID を環境変数に保存済み(ハードコードしていない)
  • 書き込み時のレート制限に対する再試行ロジックを実装済み
  • 読み取り用に適切な cacheTtl 値を設定済み(最小 60s)
  • サイズを検証済み(キー 512B、値 25MiB、メタデータ 1KB)
  • 可能な場合は一括操作を使用
  • list_complete を確認するページネーションを実装済み(keys.length ではない)
  • null 値のエラー処理を実装済み
  • レート制限の監視/アラートを設定済み

関連ドキュメント


最終更新日: 2026-01-20 パッケージのバージョン: [email protected], @cloudflare/[email protected] 変更内容: 6 件の調査結果を追加 — ホットキー/コールドキーのパフォーマンスパターン、リモートバインディング(Wrangler 4.37+)、wrangler types の環境に関する問題、CLI の --remote フラグ要件、RYOW の一貫性に関する詳細、ページネーションにおける prefix の維持

コメント