- Workers KV の結果整合性モデルと「読み取りに強い」アーキテクチャの理由
- KV namespace の作成・バインディング設定と wrangler kv コマンドでの操作
- Worker からの get / put / list / delete、および expiration(TTL)と metadata の使い方
- KV が向くユースケース・向かないユースケースの判断基準
- 無料枠と制限値(2026-08 時点)を踏まえた設計上の注意点
KV の特性 — 中央ストアとエッジキャッシュ
Workers KV は、グローバルに分散した低レイテンシの key-value ストアです。データは少数の中央データストアに保存され、アクセスされた地域の Cloudflare データセンターにキャッシュされます。この「中央保存 + エッジキャッシュ」の構造が KV のすべての性質を決めています。読み取りリクエストは次の順序で処理されます。
- アクセス元ロケーションのローカルキャッシュを確認(ヒットすれば最速。ホットキーは典型的に 500µs〜10ms 程度で応答)
- ミスした場合はリージョナル層のキャッシュへ
- それでもミスなら中央層を経て中央ストアから取得(コールドリードは遅い)
一度読まれたキーはその地域にキャッシュされるため、同じ地域からの 2 回目以降の読み取りは高速です。つまり KV は読み取りが多く書き込みが少ないワークロードに最適化されています。
一方、書き込み(put / delete)は中央ストアに対して行われ、各地のキャッシュへ自動でプッシュされるわけではありません。ここから重要な性質が生まれます。
- 結果整合性(eventually consistent): 書き込みは同じロケーションでは通常すぐ見えますが(保証はされない)、他のロケーションではキャッシュが失効するまで、最大 60 秒程度(または読み取り時の
cacheTtlの長さ)古い値が見えることがあります。 - 負のキャッシュ: 「キーが存在しない」という結果もキャッシュされます。キーを新規作成した直後でも、直前にそのキーを読んで
nullを得ていたロケーションでは、しばらくnullが返り続けます。 - 同一キーへの書き込みは 1 秒に 1 回まで: 超えると
429 Too Many Requestsエラーになります。また同一キーへの同時書き込みは後勝ち(last write wins)です。
アトミック操作やトランザクション、強整合な読み書きが必要な場合、KV は適していません。その用途には Durable Objects(第 8 章)を使います。
namespace の作成と wrangler kv コマンド
KV のデータは namespace(キーと値の集合。1 つの key-value データベース)に格納されます。namespace は npx wrangler kv namespace create <BINDING_NAME> で作成します(実行例はハンズオン 5-1)。
Worker から namespace にアクセスするには、wrangler.jsonc の kv_namespaces にバインディングを宣言します。binding は Worker コード内で env.<名前> として参照する変数名(有効な JavaScript 識別子であること)、id は作成時に発行される namespace の ID です。
{
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "<YOUR_NAMESPACE_ID>"
}
]
}
<YOUR_NAMESPACE_ID> は npx wrangler kv namespace create の出力に表示される ID に置き換えます。バインディング名と namespace 名は一致している必要はありません。TOML 派は wrangler.toml で [[kv_namespaces]] テーブルに同じ 2 キーを書きます。
wrangler の KV コマンドは 3 系統あります。主なサブコマンドを挙げます。
| 系統 | サブコマンド | 用途 |
|---|---|---|
kv namespace | create / list / rename / delete | namespace 自体の管理 |
kv key | put / get / list / delete | 単一キーの読み書き。--binding か --namespace-id のどちらか一方で対象を指定 |
kv bulk | put / get / delete | JSON ファイル経由の一括操作(REST API 経由の一括書き込みは最大 10,000 ペア) |
kv key put には --ttl(有効期間・秒)、--expiration(UNIX エポック秒)、--metadata(任意の JSON)といったオプションがあり、後述の Worker API と同じ機能を CLI から使えます。なお Wrangler 3.60.0 より前の kv:namespace のようなコロン区切り構文は非推奨です。古い記事を参考にする際は読み替えてください。
Worker からの get / put / list / delete
バインディングを宣言すると、Worker コードから 4 つのメソッドで KV を操作できます。すべて Promise を返すため await します。
export default {
async fetch(request, env, ctx): Promise<Response> {
// 書き込み(作成・上書き)
await env.MY_KV.put("user:1", "enabled");
// 読み取り(存在しないキーは null)
const value = await env.MY_KV.get("user:1");
// キーの列挙
const listed = await env.MY_KV.list({ prefix: "user:" });
// 削除(存在しないキーの削除も成功扱い)
await env.MY_KV.delete("user:1");
return Response.json({ value, keys: listed.keys });
},
} satisfies ExportedHandler<{ MY_KV: KVNamespace }>;
get() は第 2 引数で値の型を指定できます。"text"(既定)、"json"(パース済みオブジェクト)、"arrayBuffer"、"stream" の 4 種で、大きな値ほど stream / arrayBuffer が高速です。またキーの配列を渡すと一括読み取りになり(最大 100 キー、戻り値は Map)、Worker 1 呼び出しあたり 1,000 回までの外部サービス操作制限に対して 1 操作としてカウントされる利点があります(一括読み取りは text / json 型のみ)。
expiration(TTL)と metadata
put() の第 3 引数で、キーの自動失効と付随データを設定できます。
// 相対指定: 今から 1 時間後に自動削除(最小 60 秒)
await env.MY_KV.put("session:abc", token, { expirationTtl: 3600 });
// 絶対指定: エポック秒で失効時刻を指定(例: 2027-01-01 00:00 UTC)
await env.MY_KV.put("campaign", data, { expiration: 1798761600 });
// metadata: JSON 直列化で 1024 バイトまでの付随データ
await env.MY_KV.put("docs", "https://example.com", {
metadata: { createdAt: "2026-08-11" },
});
// 値と metadata を同時に読む
const { value, metadata } = await env.MY_KV.getWithMetadata("docs");
失効したキーは自動削除され、以後の読み取りは存在しないキーと同じ挙動になります。失効時刻は現在から 60 秒以上先である必要があります。metadata は list() の結果にも含まれるため、「一覧表示に必要な小さいデータは metadata に持たせ、list() 後の個別 get() をなくす」という定石があります。
get() の辺そのものです。一括 get() は往復を 1 回にまとめられますが課金はキー数ぶん発生するため、削るべきは往復ではなく読み取り操作の数です。metadata に載せればそれが 0 になります。list() のページネーション
list() は 1 回につき最大 1,000 キー(既定値かつ上限)を、キーの UTF-8 バイト順で返します。結果オブジェクトの形は次のとおりです。
{
"keys": [
{ "name": "user:1", "expiration": 1798761600, "metadata": { "plan": "free" } }
],
"list_complete": false,
"cursor": "6Ck1la0VxJ0djhidm1MdX2FyD"
}
続きの有無は keys が空かどうかではなく list_complete で判定します。削除・失効直後のキー(tombstone)を走査する回では、keys が空でも続きが存在することがあるためです。list_complete が false の間、cursor を次の list({ cursor }) に渡して繰り返します。prefix で絞り込んでいる場合は 2 ページ目以降も同じ prefix を渡し続けます。
keys の中身ではなく list_complete です。色を変えた矢印が cursor を持ち回る経路で、削除・失効直後のキーだけを走査した回は keys が空のままここを 1 周します。ユースケースと制限・無料枠
「読み取り超高速・書き込みは伝播に時間がかかる」という特性から、向き不向きは明確です。
| 判定 | ユースケース | 理由・代替 |
|---|---|---|
| 向く | アプリ設定・機能フラグの配布、A/B テスト設定 | 書き込み稀・読み取り多。全世界のエッジで低レイテンシに読める |
| 向く | セッションデータ・認証トークン・API キーの保存 | 高頻度読み取り + 更新頻度低。expirationTtl で自動失効できる |
| 向く | 外部 API レスポンスや静的アセットのキャッシュ | Cache API と異なりデータセンター間で共有され、削除まで永続する |
| 向かない | 同一キーを毎秒何十回も更新するカウンタやランキング | 同一キー 1 write/秒の制限に衝突。Durable Objects を使う |
| 向かない | アトミック操作・トランザクション・強整合が必要な処理 | 結果整合のため。Durable Objects の強整合ストレージを使う |
| 向かない | リレーショナルなクエリ、大容量ファイル | 前者は D1(第 7 章)、後者は R2(第 6 章)の領分 |
主要な制限値と無料枠は次のとおりです(2026-08 時点)。KV は Free プランでも使えます。
| 項目 | Free プラン | Paid プラン |
|---|---|---|
| 読み取り | 100,000 回/日 | 1,000 万回/月 込み、超過 $0.50/100 万回 |
| 書き込み(異なるキー) | 1,000 回/日 | 100 万回/月 込み、超過 $5.00/100 万回 |
| 削除 / list | 各 1,000 回/日 | 各 100 万回/月 込み、超過 $5.00/100 万回 |
| ストレージ | 1 GB | 1 GB 込み、超過 $0.50/GB-月 |
| 同一キーへの書き込み | 1 回/秒 | 1 回/秒 |
| キー / 値 / metadata サイズ | 512 バイト / 25 MiB / 1,024 バイト(両プラン共通) | |
Free プランの日次上限は毎日 00:00 UTC(日本時間 9:00)にリセットされ、超過するとその種類の操作はエラーになります。ダッシュボードや wrangler からの操作も課金対象の操作数にカウントされる点、存在しないキーの読み取り(null が返るもの)も読み取り 1 回と数えられる点に注意してください。失効で削除されたキーは、以後ストレージ使用量の課金対象に含まれません。
Worker プロジェクトと KV namespace を作成し、wrangler kv コマンドで最初のキーを読み書きします。所要 10 分。Free プランで完結します。
- プロジェクトを作成します。質問には「Hello World example」「Worker only」「TypeScript」、デプロイは「No」で答えます。
npm create cloudflare@latest -- kv-hands-on cd kv-hands-on - namespace を作成します。namespace のタイトルは Worker 名とバインディング名を連結した名前になります。
npx wrangler kv namespace create COUNTER # 成功時の出力 🌀 Creating namespace with title "kv-hands-on-COUNTER" ✨ Success! Add the following to your configuration file in your kv_namespaces array: { "kv_namespaces": [ { "binding": "COUNTER", "id": "<YOUR_NAMESPACE_ID>" } ] } - 出力の
kv_namespacesブロックをwrangler.jsoncにそのまま追記します(idは自分の環境で発行された値を使います)。 - リモートの namespace にキーを書き込み、読み返し、一覧します。
--remoteを付けない場合はローカルストレージが対象になります。npx wrangler kv key put --binding=COUNTER "hello" "world" --remote npx wrangler kv key get --binding=COUNTER "hello" --text --remote npx wrangler kv key list --binding=COUNTER --remote
get が world を、list が [{"name": "hello"}] を表示すれば成功です。ダッシュボードの Workers KV ページでも同じキーを確認できます。
KV の読み書きを Worker に組み込み、アクセスのたびに数字が増えるカウンタを作ります。所要 15 分。
src/index.tsを次の内容に置き換えます。export default { async fetch(request, env, ctx): Promise<Response> { const url = new URL(request.url); if (url.pathname !== "/") { return new Response("Not Found", { status: 404 }); } const current = await env.COUNTER.get("visits"); const count = (current === null ? 0 : parseInt(current, 10)) + 1; await env.COUNTER.put("visits", String(count)); return new Response(`visits: ${count}`); }, } satisfies ExportedHandler<{ COUNTER: KVNamespace }>;- ローカルで動作確認します。
wrangler devは既定でローカル版 KV を使うため、本番データを汚さず試せます。npx wrangler dev # 別ターミナルで数回実行 curl http://localhost:8787/ - 実行のたびに
visits: 1、visits: 2… と増えることを確認したら、npx wrangler deployでデプロイし、workers.dev の URL でも確認します。
このカウンタは KV の教材としては最適ですが、本番の厳密な集計には不向きです。同一キーへの書き込みは 1 回/秒までで、複数拠点からの同時アクセスでは結果整合性により読み取った値が古く、カウントを取りこぼします。「読んで加算して書く」を安全に行いたい場合は Durable Objects(第 8 章)が正解です。この限界を体感することも本ハンズオンの目的です。
コードとリダイレクト先を KV に保存し、expirationTtl と metadata を実戦投入する短縮 URL サービスの骨格を作ります。所要 20 分。
- リンク保存用の namespace を作成し、5-1 と同様に出力を
wrangler.jsoncのkv_namespaces配列に追記します。npx wrangler kv namespace create LINKS src/index.tsを次の内容に置き換えます。POST でリンクを登録し、GET で 302 リダイレクトします。interface Env { COUNTER: KVNamespace; LINKS: KVNamespace; } export default { async fetch(request, env, ctx): Promise<Response> { const url = new URL(request.url); if (request.method === "POST" && url.pathname === "/api/links") { const body = (await request.json()) as { code?: string; target?: string }; if (!body.code || !body.target) { return new Response("code and target are required", { status: 400 }); } await env.LINKS.put(body.code, body.target, { expirationTtl: 60 * 60 * 24 * 30, // 30 日で自動失効 metadata: { createdAt: new Date().toISOString() }, }); return Response.json({ code: body.code, target: body.target }); } const target = await env.LINKS.get(url.pathname.slice(1)); if (target === null) { return new Response("Not Found", { status: 404 }); } return Response.redirect(target, 302); }, } satisfies ExportedHandler<Env>;npx wrangler devを起動し、別ターミナルからリンクを登録してリダイレクトを確認します。curl -X POST http://localhost:8787/api/links \ -H "Content-Type: application/json" \ -d '{"code": "docs", "target": "https://developers.cloudflare.com/kv/"}' curl -i http://localhost:8787/docs
2 つ目の curl で HTTP/1.1 302 Found と location: https://developers.cloudflare.com/kv/ が返れば成功です。登録済みリンクの一覧表示を作る場合は、list() の結果に含まれる metadata(登録日時)をそのまま使えば、キーごとの get() は不要です。
つまずきポイント
wrangler dev と kv key put は既定でローカル版 KV を対象にします。リモート(本番)に書くには --remote フラグが必要です。逆に wrangler dev からリモートの namespace を読みたい場合は、バインディング設定に "remote": true を追加します。「書いたはずのキーが null」の最有力容疑者は、このローカル / リモートの取り違えです。
結果整合性による正常な挙動です。他ロケーションには最大 60 秒程度(または cacheTtl の長さ)で伝播します。「存在しない」という結果もキャッシュされるため、作成直後のキーが null のままに見えることもあります。書き込み直後の読み取りに依存する設計自体を見直してください。
同一キーへの書き込みが 1 秒に 1 回を超えています。1 回の Worker 呼び出し内の複数書き込みを 1 回にまとめる、書き込み先を複数キーに分散する、指数バックオフ付きリトライを実装する、のいずれかで対処します。恒常的に高頻度更新が必要なら Durable Objects への移行を検討してください。
失効時刻は現在から 60 秒以上先が必須です。expirationTtl に 60 未満の値を渡す、あるいは expiration に 60 秒未満の近未来や過去のエポック秒を渡すと失敗します。60 秒未満の短期キャッシュがほしい場合は、読み取り側の cacheTtl(最小 30 秒)や Cache API の利用を検討してください。
理解度チェック
Q1. KV に書き込んだ値が、他のリージョンから見えるようになるまでの整合性モデルは?
結果整合性(eventually consistent)です。書き込んだロケーションでは通常すぐ見えますが(保証なし)、他のロケーションではキャッシュの失効まで、最大 60 秒程度または読み取り時の cacheTtl の長さだけ古い値が見えることがあります。
Q2. 同一キーに対して許される書き込み頻度と、超過した場合に起きることは?
同一キーへの書き込みは 1 秒に 1 回までです(プランを問わず共通)。超過すると KV PUT failed: 429 Too Many Requests エラーが発生します。異なるキーへの書き込みであればこの制限はかかりません。
Q3. put() でキーを 30 秒後に自動失効させることはできる?
できません。失効時刻(expiration / expirationTtl とも)は現在から 60 秒以上先である必要があります。なお読み取りキャッシュの cacheTtl は別物で、こちらの最小値は 30 秒です。
Q4. list() で「まだ続きのキーがあるか」を正しく判定する方法は?
list_complete が false かどうかで判定し、cursor を次の呼び出しに渡します。削除・失効直後のキーを走査する回では keys が空配列でも続きが存在しうるため、keys の空判定では不十分です。
Q5. Free プランの KV で 1 日にできる読み取りと書き込みの回数は?(2026-08 時点)
読み取り 100,000 回/日、書き込み 1,000 回/日(削除・list も各 1,000 回/日)です。上限は毎日 00:00 UTC にリセットされ、超過するとその種類の操作はエラーになります。ストレージは 1 GB までです。
まとめと次章へ
- KV は中央ストア + エッジキャッシュ構成の結果整合な key-value ストアで、読み取り多・書き込み少のワークロードに最適化されている
- 書き込みの他ロケーションへの伝播は最大 60 秒程度。負のキャッシュ・同一キー 1 write/秒・後勝ちの性質を設計に織り込む
wrangler kv namespace createで namespace を作り、wrangler.jsoncのkv_namespacesでバインディングしてenv.名前から操作する- Worker からは
get(型指定・一括読み取り・cacheTtl)/put(expirationTtl・metadata)/list(prefix・cursor)/deleteの 4 メソッド - 設定配布・セッション・キャッシュに向き、高頻度カウンタ・トランザクション・リレーショナルデータには向かない。後者は Durable Objects / D1 の領分
次章は、25 MiB の値上限を超える世界 — 画像・動画・任意のファイルを格納する、エグレス無料のオブジェクトストレージ R2 を扱います。
- How KV works — 中央ストアとキャッシュ階層、整合性の公式解説
- Read key-value pairs / Write key-value pairs — get / put の全パラメータ
- Wrangler KV commands — CLI リファレンス
- Limits / Pricing — 制限値と料金の最新情報
- Choose a data or storage product — KV と R2 / D1 / Durable Objects の使い分け