Cloudflare D1とKVの使い分け:静的サイトに小さい動的機能を足すとき
このサイトは Cloudflare Pages で配信している静的ブログで、ページはすべてビルド時に生成しています。以前の記事で WorkersとPagesの使い分け を整理したときに、「動的な処理が要るなら Workers へ」とは書きました。ただ、そこで終わりにしなかった問いが1つ残っています。状態(データ)はどこに置くのか、です。
問い合わせの内容を保存したい。記事ごとに「いいね」の数を数えたい。そういう「小さいけれど状態を持つ機能」を足そうとすると、置き場の候補として名前が挙がるのが D1 と Workers KV です。名前は並べて紹介されることが多いのですが、性格はかなり違います。
この記事の前提
このサイトでは、現時点で D1 も KV も使っていません。これは「使ってみた」報告ではなく、足すとしたら何のためか、どちらを選ぶかを先に決めておくための検討メモです。仕様や料金は Cloudflare の公式ドキュメントで確認した範囲で書いています。
まず性格の違いを押さえる
一言でいうと、KV は「キーを指定して値を出し入れする箱」、D1 は「SQL で問い合わせる小さなデータベース」です。公式ドキュメントでも、KV は設定データやセッションのようなキー・バリューの用途、D1 はユーザープロフィールや注文のようなリレーショナルなデータの用途、と対比されています。
| 観点 | Workers KV | D1 |
|---|---|---|
| データの形 | キーと値のペア | テーブルと行(リレーショナル) |
| 土台 | 中央のストア+各拠点へのキャッシュ | SQLite ベースのサーバーレスDB |
| 取り出し方 | キーを指定して取得(前方一致の一覧も可) | SQL で条件・結合・集計まで書ける |
| 整合性 | 結果整合性。書いた場所では通常すぐ見えるが、他の拠点では最大60秒以上かかることがある | 書き込みがそのまま正になる前提で扱える |
| 書き込みの癖 | 同じキーへの書き込みは1秒に1回まで | 1つのDBはクエリを1つずつ処理する |
| 得意な読み方 | 同じ値が何度も読まれる読み取り中心の用途 | 読み取り比率が高い業務データ全般 |
| 向かないもの | 秒単位で増えるカウンタ、競合する更新 | 巨大な単一DB前提の設計(1DBの上限が前提に小さい) |
効いてくるのは整合性の差です。KV は世界中の拠点で速く読める代わりに、「さっき書いた値が、別の場所からはまだ古いまま見える」時間があります。設定値のように「1分古くても困らない」ものなら問題になりませんが、数字を正確に数えたい用途では、この時点で候補から外れます。
もう1つ、KV には同じキーへの書き込みが1秒に1回までという制限があります。人気記事の閲覧カウンタを1つのキーで数える設計にすると、アクセスが集中した瞬間に書き込みが弾かれます。これは設定ミスではなく、製品の性格です。
用途ごとに振り分けてみる
個人ブログで「状態が欲しくなる」場面を思いつくままに挙げて、どちら向きか(あるいはどちらでもないか)を振り分けてみます。
| やりたいこと | 向き | 理由 |
|---|---|---|
| 閲覧数カウンタ | D1(ただし要検討) | 加算が競合するので KV の同一キー制限と結果整合性が噛み合わない。SQL の UPDATE で寄せる |
| いいねボタン | D1 | 件数の正確さと、二重押し防止を制約で守りたい。行として積む形が扱いやすい |
| 問い合わせ内容の保存 | D1 | 日時・宛先・本文を並べて後から見返す用途。検索や並び替えは SQL の領分 |
| 設定値・公開フラグの保持 | KV | たまに書き換えて何度も読む、古さが数十秒あっても困らない、という KV の典型用途 |
| 人気記事ランキングの表示 | どちらでもない | 表示側はビルド時に焼き込めば足りる。集計元データを自前で持つかどうかが先の話 |
| 記事データそのもの | どちらでもない | このサイトでは Markdown+ビルド時生成が正で、移す理由がない |
振り分けてみると、ブログの用途は意外と D1 側に寄ります。「記録として残す」「後から条件で引く」「数を間違えたくない」——この3つのどれかが入ると、キー・バリューでは受け止めにくくなるからです。逆に KV が気持ちよくはまるのは、フィーチャーフラグや表示設定のような「読む回数に対して書く回数が桁違いに少ない値」で、公式ドキュメントが例に挙げる設定データやセッションもこの系統でした。
カウンタについては、そもそも論もあります。このサイトの閲覧傾向は解析側で見えているので、「記事ページに数字を出すために」わざわざ書き込み経路を持つのか、という問いは残ります。表示したいのか、数えたいのかを分けると、要らない動的化を1つ減らせます。
判断に使っている物差し
「書いた直後に正しく読めないと困るか」→ 困るなら D1。「条件で引いたり集計したりするか」→ するなら D1。「同じ値を何度も速く読みたいだけか」→ それなら KV。この3問で、このサイト規模の用途はだいたい振り分けられます。
先に「ビルド時で済むもの」を落とす
D1 と KV の前に、静的生成で片付くものを落としておくのが順番だと思っています。実行時にデータストアへ聞きに行くのは、ビルド時に決められないものだけにしたいからです。
- 値が変わるのが記事の公開・更新のタイミングだけ → ビルド時に埋め込む
- 一覧・並び替え・絞り込みが公開時点で固定できる → 生成済みHTMLと Pagefind の範囲で足りる
- 訪問者の操作でその場で変わる → ここで初めて D1/KV の検討に入る
- 訪問者から受け取って保存する → 保存先として D1 を第一候補にする
人気記事ランキングが良い例で、「先月の閲覧数で並べた上位5件」を出すだけなら、集計結果をビルド時に焼き込む静的な部品で十分です。実行時のストアが要るのは、数字そのものを訪問者のアクセスで増やし続ける設計にしたときだけ。機能の見た目ではなく、データがいつ変わるかで線を引くと、動的化の範囲はかなり小さくなります。
無料枠の考え方
どちらも Workers の無料プランの範囲で試せます。ただし枠の数え方が正反対に近いので、数字を並べると性格の違いがもう一度見えてきます(以下は執筆時点に公式ドキュメントで確認した無料プランの上限です)。
| 項目 | Workers KV(無料) | D1(無料) |
|---|---|---|
| 読み取り | 100,000回/日(キー単位) | 500万行/日(読み取った=スキャンした行数) |
| 書き込み | 1,000回/日(キーが異なるもの) | 100,000行/日 |
| 保存容量 | 1 GB | 合計 5 GB(1DBあたり上限 500 MB、DBは10個まで) |
注目点は2つ。KV は読み取りに対して書き込み枠がとても小さい(1,000回/日)ことで、「たまに書いて、たくさん読む」前提が料金設計にも表れています。D1 は逆に書き込み枠が広い代わりに、読み取りが「返した行数」ではなく「スキャンした行数」で数えられます。インデックスのない条件で大きなテーブルを舐めると、結果が少なくても行数は消費する。無料枠の中で使うなら、D1 はインデックスを最初から設計に入れる話になります。
上限の扱いも実務的には大事で、無料プランで D1 の日次上限に達すると、クエリはエラーになって翌日(00:00 UTC リセット)まで止まります。請求が跳ねる形ではないので、個人サイトの実験用としては安心な側の仕様です。有料プラン(Workers Paid、月額最低5ドルから)に上げると両方とも枠が大きく広がりますが、このサイトの規模で先に必要になる場面は想像しにくい、というのが正直なところです。
料金・上限についての注記
この手の数字は変わりやすいので、導入を決める前に公式の料金ページで最新を確認してください。この記事の数字も、公開時点の確認に基づくものとして読んでください。
このサイトでの結論
現時点の結論は「どちらもまだ入れない」です。足したい機能の筆頭である問い合わせ保存は D1 候補と決めましたが、フォーム自体の設計(Turnstile の検討メモ で触れた話)が先で、保存先だけ先行して用意する理由がありません。設定値を KV に置く案も、置くべき設定がまだ無いので保留です。
ただ、検討を先に済ませておく意味はありました。「状態が欲しくなったら、まずビルド時で済まないかを見て、残ったら記録系は D1、読み取り専用の値は KV」という順番が決まったので、次に何か足したくなったときに製品比較からやり直さずに済みます。静的サイトの軽さは、足さないものを決めることで保たれるのだと思います。
参考
- Choosing a data or storage product — Cloudflare Workers docs: https://developers.cloudflare.com/workers/platform/storage-options/
- How KV works — Cloudflare Workers KV docs: https://developers.cloudflare.com/kv/concepts/how-kv-works/
- Limits — Cloudflare Workers KV docs: https://developers.cloudflare.com/kv/platform/limits/
- Pricing — Cloudflare Workers docs: https://developers.cloudflare.com/workers/platform/pricing/
- Pricing — Cloudflare D1 docs: https://developers.cloudflare.com/d1/platform/pricing/
- Limits — Cloudflare D1 docs: https://developers.cloudflare.com/d1/platform/limits/