Cloudflare D1を個人開発で使ってみた【Cloudflare運用記録 #5】
はじめに
こんにちは。
なかのひとカンパニーの archer です。
前回は、Next.jsをCloudflare Workersで動かす構成について書きました。
今回は、そのWorkersから使っているデータベース、
Cloudflare D1
についてまとめます。
D1はSQLiteベースのサーバーレスデータベースです。
自分の場合は、
Next.js
↓
Cloudflare Workers
↓
D1
という構成で使っています。
最初に触ったときに便利だと感じたのは、
データベース用のサーバーを自分で用意しなくていいこと
でした。
PostgreSQLやMySQLを自分で立てる必要がない。
接続先サーバーを管理する必要もない。
WorkersからBindingして、そのまま使える。
個人開発ではかなり扱いやすいデータベースだと感じています。
D1はSQLiteに近い
D1はSQLiteをベースにしています。
そのため、SQL自体はかなり馴染みやすいです。
たとえば、
CREATE TABLE movies (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
created_at TEXT NOT NULL
);
のようにテーブルを作れます。
取得する場合も普通のSQLです。
SELECT *
FROM movies
ORDER BY created_at DESC;
PostgreSQLやMySQLを触ったことがあれば、基本的な操作で大きく迷うことは少ないと思います。
ただし、
SQLiteとまったく同じものとして扱えばいいわけではない
という点は意識しています。
D1はCloudflare上で動くサービスです。
ローカルのSQLiteファイルを直接本番へ置いているわけではありません。
WorkersからBindingして使う
D1はWorkersへBindingして利用できます。
たとえばWranglerの設定では、こんな形です。
{
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database",
"database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}
binding を DB にすると、Worker側では、
env.DB
として利用できます。
たとえば、
const result = await env.DB
.prepare("SELECT * FROM movies ORDER BY created_at DESC")
.all();
のような形です。
自宅サーバーでPostgreSQLを使っていたときは、
ホスト
ポート
ユーザー
パスワード
DB名
などの接続情報を管理していました。
D1の場合は、WorkersとのBindingとして扱えるので、
アプリとDBの接続をCloudflare側へ寄せられる
のが便利でした。
ローカルDBと本番DBを分けられる
D1を使ううえで、かなり重要だと思っているのがこれです。
Wranglerでは、
wrangler d1 execute my-database --local
のように --local を付けると、ローカル環境のD1を操作できます。
たとえば、
wrangler d1 execute my-database --local --command "SELECT * FROM movies;"
とすれば、ローカルDBに対してSQLを実行できます。
逆に、リモート側を操作するときは --remote を使います。
wrangler d1 execute my-database --remote --command "SELECT * FROM movies;"
この、
--local
--remote
の違いはかなり重要です。
本番DBを触るつもりがなかったのに、リモートへSQLを実行してしまう。
これは普通に怖いです。
そのため、自分の場合は、
普段の開発ではlocalを使い、本番操作は明示的にremoteを指定する
という考え方にしています。
MigrationでDB変更を管理する
テーブル構造を変更するときはMigrationを使えます。
たとえば、
wrangler d1 migrations create my-database add_movies_table
を実行すると、Migration用のSQLファイルを作成できます。
イメージとしては、
migrations/
├─ 0001_create_movies.sql
├─ 0002_add_theaters.sql
└─ 0003_add_indexes.sql
という形です。
中身は普通のSQLです。
ALTER TABLE movies
ADD COLUMN description TEXT;
ローカルへ適用する場合は、
wrangler d1 migrations apply my-database --local
本番へ適用する場合は、
wrangler d1 migrations apply my-database --remote
です。
Migrationファイル自体をGitで管理できるので、
コード変更
+
DB変更
を同じPRで確認できます。
これはGitHub中心で開発している自分にはかなり相性がいいです。
本番Migrationはやっぱり怖い
便利ではありますが、
wrangler d1 migrations apply my-database --remote
を実行するときは、やはり少し緊張します。
コードは戻せても、データ変更は簡単に戻せない場合があります。
そのため、本番へ適用する前に、
Migration内容確認
↓
localへ適用
↓
アプリ動作確認
↓
CI確認
↓
本番へ適用
という順番にしています。
特に、
DROP
DELETE
ALTER
UPDATE
などが含まれている場合は注意します。
「Migrationファイルがあるから安全」
ではありません。
Migrationは、
DB変更を管理しやすくする仕組みであって、DB変更そのものを安全にしてくれるわけではない
と考えています。
ローカルと本番は別物
D1のローカル開発では、本番とは別のローカルDBが作られます。
これはとても便利です。
ローカルで、
DELETE FROM movies;
を実行しても、本番データには影響しません。
開発中に何度でもデータを入れ直せます。
一方で、当然ながら、
ローカルで動いたから本番でも絶対に同じ
とは限りません。
本番には実データがあります。
データ量も違います。
過去のMigrationもあります。
そのため、ローカル確認と本番確認は別物として考えています。
D1の料金は「クエリ回数」ではない
D1の料金で最初に少し分かりにくかったのが、
SQLを何回実行したかだけで決まるわけではない
というところです。
D1では主に、
- Rows read
- Rows written
- Storage
で利用量を見ます。
Freeプランでは、2026年9月時点で、
Rows read
500万行 / 日
Rows written
10万行 / 日
Storage
合計5GB
まで利用できます。
さらにFreeプランでは、
DB数
10個
1DBあたり
最大500MB
という制限があります。
500万Readなら余裕なのか
数字だけを見ると、
500万行 / 日
はかなり多く見えます。
ただし注意したいのが、
500万クエリではなく、500万行Read
というところです。
たとえば、
SELECT *
FROM movies;
で1万行を読み込む処理を500回実行すると、
10,000 × 500
=
5,000,000
になります。
アクセス数自体は500回でも、読み取る行数によっては無料枠へ到達できます。
そのため、
必要な列だけ取る
必要な行だけ取る
INDEXを使う
無駄な全件取得を避ける
という普通のDB設計が、そのまま料金にも影響します。
無料枠が大きいからSQLを気にしなくていい、というわけではありません。
Writeは10万行 / 日
WriteはFreeで、
100,000 rows / day
です。
普通の個人サービスなら、かなり余裕があると思います。
たとえば、
ユーザー登録
お気に入り
閲覧履歴
管理画面からのデータ登録
くらいなら、すぐに10万行へ到達するケースは少なそうです。
一方、
大量データの定期収集
ログの保存
スクレイピング結果の大量更新
などでは、Write数も意識した方がよさそうです。
個人開発でも、バッチ処理を作ると一気に書き込み数が増えることがあります。
Free上限を超えるとどうなるか
ここは重要です。
Freeプランでは、
上限を超えた分だけ自動的に課金されて、そのまま動き続ける
わけではありません。
1日のRead / Write上限へ到達すると、その日のD1クエリが実行できなくなります。
Storage上限へ到達すると、新しいデータの追加などができなくなります。
つまり本番サービスでは、
無料枠到達
↓
DB操作失敗
↓
サービスに影響
という可能性があります。
「料金が発生しないから安心」
ではなく、
無料枠を超えると止まる可能性がある
という点まで考える必要があります。
Paidになるとどうなるか
Workers Paidでは、D1にも月単位の無料利用分が含まれます。
2026年9月時点では、
Rows read
月250億行まで含む
Rows written
月5,000万行まで含む
Storage
5GBまで含む
となっています。
超過すると従量課金です。
D1そのものに、
DBサーバーを24時間起動しているから毎月いくら
という料金はありません。
使っていなければCompute料金が発生する、という仕組みではなく、
利用量に応じて課金される
形です。
小さなサービスとの相性がいいと感じる理由のひとつです。
1DB 500MBは少し意識する
Freeプランではアカウント全体で5GBあります。
しかし、
1DB最大500MB
という制限があります。
つまり、
5GBあるから1つのDBへ5GB入れられる
わけではありません。
この500MBは、サービスによっては先に気になる可能性があります。
特に、
- 長期間のログ
- 大量の履歴
- HTML全文
- 大きなJSON
- バイナリデータ
などを保存すると増えやすいです。
画像などの大きなファイルをD1へ持たせるより、
D1
→ ファイル情報
R2
→ 実ファイル
という形の方が使いやすいと思っています。
次回扱うR2との役割分担です。
Time Travelもある
D1にはTime Travelという仕組みがあります。
データベースを過去の状態へ復元するための機能です。
Freeでは、
7日
Paidでは、
30日
の復元期間があります。
本番DBを扱う以上、
「間違えたらどう戻すか」
は考えておきたいところです。
Migrationだけではなく、
復旧手段があることも本番運用では重要
だと感じています。
今回使ったサービスの料金
D1 Freeでは、主に以下の範囲で利用できます。
| 項目 | Free |
|---|---|
| Rows read | 500万行 / 日 |
| Rows written | 10万行 / 日 |
| Storage | 合計5GB |
| DB数 | 10 |
| 1DB最大容量 | 500MB |
| Time Travel | 7日 |
小規模なWebサービスなら、
月額0円で本番DBまで持てる
というのはかなり便利です。
一方で、個人的に気をつけたいのは、
アクセス数
より、
SQLが何行読んでいるか
1DBが500MBを超えないか
大量Writeをする処理がないか
の方です。
D1を使うなら、Cloudflare Dashboardなどで利用量も確認しながら使うのがよさそうです。
※料金・無料枠・制限は2026年9月時点のCloudflare公式情報をもとにしています。
最新情報はCloudflare公式ドキュメントを確認してください。
まとめ
今回はCloudflare D1を個人開発で使ったときの構成や注意点についてまとめました。
自分が便利だと感じているのは、
- DBサーバーを管理しなくていい
- WorkersからBindingで使える
- SQLiteに近いSQLで扱える
- ローカルDBと本番DBを分けられる
- MigrationをGitで管理できる
- 小規模なら無料で始められる
というところです。
一方で、
-
--localと--remoteを間違えない - Migration前に内容を確認する
- Readはクエリ数ではなく行数を見る
- Freeでは1DB 500MB
- 無料枠到達時はDB操作が失敗する
といった点は意識する必要があります。
D1を使って一番感じたのは、
データベースを管理する仕事は減るけれど、データを管理する責任までなくなるわけではない
ということでした。
サーバーを立てなくてもDBは使える。
でも、本番データをどう変更するか。
どう戻すか。
どこまで増えるか。
そこは結局、自分で考える必要があります。
それでも個人開発では、
DBサーバーを用意せず、そのままWorkersから使える
メリットはかなり大きいです。
次回は、
Cloudflare R2を使ってファイルを保存する
というテーマで、画像やJSONなどをオブジェクトストレージへ保存する構成についてまとめます。
D1とR2をどう使い分けるのか、無料枠やEgress無料についても書く予定です。
