Web開発 2026年5月10日
Cloudflare D1
D1 は Cloudflare のサーバーレス SQL データベースであり、SQLite 互換のクエリと Workers バインディングで動作する。1 アカウントあたり最大 5 万 DB を作成でき、テナントごと・ユーザーごとに DB を分ける「水平スケールアウト型」設計が前提となる。
D1
一行サマリ
D1 は Cloudflare のサーバーレス SQL データベースであり、SQLite 互換のクエリと Workers バインディングで動作する。1 アカウントあたり最大 5 万 DB を作成でき、テナントごと・ユーザーごとに DB を分ける「水平スケールアウト型」設計が前提となる。
解決する課題(Why)
従来のマネージド RDB は「常時起動するインスタンス」を前提とし、Workers のようなエッジ実行環境からは接続プールやレイテンシで相性が悪い。D1 は以下を解決する。
- 接続プール不要。Workers バインディング経由で直接クエリを発行できる。
- スケール to ゼロ。クエリを実行しない時間は課金されない。
- DB 作成コストが実質ゼロ。マルチテナント SaaS で「テナント=1 DB」設計が経済合理的に成立する。
- バックアップ/PITR が標準装備(Time Travel)で運用負荷を持たない。
主要機能(What)
- SQLite ベース:SQLite の SQL セマンティクスと型システムをそのまま採用。スキーマ・クエリは標準 SQLite と互換。
- Workers Binding API / HTTP REST API / Wrangler CLI の 3 経路でアクセス可能。
- Time Travel:過去 30 日(Free は 7 日)の任意の 1 分単位に DB を復元できるポイントインタイムリカバリ。バックアップ設定は不要で常時有効。
- Global Read Replication:読み込みリードレプリカをグローバルに自動配置し、リード遅延を低減。書き込みは引き続きプライマリ(単一の Durable Object)に集約。
- Batch / Prepared Statement:
db.batch()でトランザクション的に複数文を実行。バインドパラメータは最大 100。 - インポート/エクスポート:
wrangler d1 execute --fileで SQL ダンプを取り込み可能(最大 5 GB)。
アーキテクト視点:いつ選ぶか
適しているシーン
- Workers / Pages を前提としたエッジアプリケーションの永続層。
- マルチテナント SaaS で「テナントごとに DB を分離したい」要件。1 アカウント 5 万 DB が利用可能。
- 1 DB あたり 10 GB 以下に収まる、明確に分割できるドメインデータ。
- 平均レスポンス 1 ms 〜 数十 ms の OLTP ワークロードで、書き込みが秒間数十〜数百以下に収まる規模。
- バックアップ運用を持ちたくない小〜中規模プロジェクト。
適していないシーン(重要)
- 書き込み TPS が高いワークロード:各 D1 DB は単一の Durable Object に紐づき、シングルスレッドで逐次処理される。平均クエリ 1 ms なら理論上限は約 1000 QPS、100 ms なら 10 QPS。書き込みは数 ms 単位で複数拠点に永続化されるため、単一 DB で秒間数百以上の書き込みは破綻する。
- 1 DB で 10 GB を超えるデータ:DB あたり 10 GB は引き上げ不可。シャーディング設計が必須。
- 長時間クエリ・大量更新:単一 SQL の実行上限は 30 秒。数十万行を一括 UPDATE / DELETE する DDL バッチは平台限界に抵触するため、1000 行単位等に分割する必要がある。
- PostgreSQL 固有機能(拡張、JSONB、PostGIS、CTE 上の高度な最適化、ストアド)に依存するワークロード。
- 強整合の地理分散書き込み:書き込みは単一プライマリ Durable Object に集約されるため、書き込み元のリージョンによってはレイテンシが大きくなる。
競合・代替
| 観点 | D1 | Neon (Postgres) | Turso (libSQL) | Supabase (Postgres) | PlanetScale (MySQL) | Hyperdrive + RDS/Postgres |
|---|---|---|---|---|---|---|
| エンジン | SQLite | PostgreSQL | SQLite (libSQL) | PostgreSQL | MySQL/Vitess | PostgreSQL/MySQL |
| デプロイ形態 | サーバーレス(Workers 統合) | サーバーレス(ブランチ機能) | エッジ分散 | マネージド | マネージド | 外部 DB を Workers 高速化 |
| スケール to ゼロ | 対応 | 対応 | 対応 | 一部 | 非対応 | 外部 DB に依存 |
| 書き込みスケール | 単一 DO(弱) | 単一プライマリ+スケール | 単一プライマリ | 強い | 非常に強い(シャーディング) | バックエンド依存 |
| マルチテナント DB 分離 | 5 万 DB 無料 | プロジェクト/ブランチ単位 | 数万 DB 対応 | 1 プロジェクト 1 DB | DB 数に課金 | 不向き |
| Workers との相性 | ネイティブ | Hyperdrive 経由 | ネイティブ/HTTP | Hyperdrive 経由 | Hyperdrive 経由 | ネイティブ |
| バックアップ/PITR | Time Travel 30 日 | ブランチ+PITR | ブランチ | PITR(有料) | ブランチ | RDS 機能 |
| 主用途 | エッジ OLTP・テナント分離型 SaaS | フル機能 Postgres が必要なエッジ | エッジ分散 SQLite | Postgres + Auth + Storage 統合 | 大規模 OLTP | 既存 Postgres 資産の延命 |
選定基準:Workers 中心かつ DB を細かく分けられるなら D1、Postgres の機能や単一 DB の書き込みスループットが必要なら Neon+Hyperdrive、エッジ書き込み分散重視なら Turso、フルスタック BaaS なら Supabase。
料金モデルの要点
| 項目 | Workers Free | Workers Paid |
|---|---|---|
| Rows read | 500 万 / 日 | 月 250 億まで無料、超過分 $0.001 / 100 万行 |
| Rows written | 10 万 / 日 | 月 5000 万まで無料、超過分 $1.00 / 100 万行 |
| Storage | 合計 5 GB | 5 GB 含む、超過分 $0.75 / GB-月 |
| データ転送(egress) | 無料 | 無料 |
| 読み込みレプリカ | 追加料金なし | 追加料金なし(通常の rows_read 課金のみ) |
- 課金はクエリ+ストレージのみ。インスタンス時間課金なし、転送量課金なし。
- Rows read はスキャン行数。フルスキャンは行数ぶん丸ごと加算されるため、インデックス設計が直接コストに効く。
- Rows written:INSERT / UPDATE / DELETE で書き込まれた行数。インデックスがある列を更新すると追加で書き込みが発生する。
- 100 KB の行も 1 KB の行も 1 行は 1 行としてカウントされる。
- 各クエリのレスポンスに含まれる
meta.rows_read/meta.rows_writtenで実測可能。
CLI / IaC 操作例
Wrangler
# DB 作成
wrangler d1 create my-app-db
# wrangler.toml にバインディング登録
# [[d1_databases]]
# binding = "DB"
# database_name = "my-app-db"
# database_id = "<UUID>"
# マイグレーション
wrangler d1 migrations create my-app-db init
wrangler d1 migrations apply my-app-db --remote
# 任意 SQL の実行
wrangler d1 execute my-app-db --remote --command "SELECT count(*) FROM users"
# ダンプ取り込み
wrangler d1 execute my-app-db --remote --file ./seed.sql
# Time Travel
wrangler d1 time-travel info my-app-db
wrangler d1 time-travel restore my-app-db --bookmark <bookmark>
Drizzle ORM
// schema.ts
import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull().unique(),
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
});
// worker.ts
import { drizzle } from "drizzle-orm/d1";
import { eq } from "drizzle-orm";
import { users } from "./schema";
export interface Env { DB: D1Database }
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const db = drizzle(env.DB);
const url = new URL(req.url);
const email = url.searchParams.get("email");
if (!email) return new Response("email required", { status: 400 });
const found = await db.select().from(users).where(eq(users.email, email)).all();
return Response.json(found);
},
};
# Drizzle Kit でスキーマからマイグレーション SQL を生成 → wrangler で適用
npx drizzle-kit generate
wrangler d1 migrations apply my-app-db --remote
制限・注意点
| 項目 | 値 |
|---|---|
| 1 アカウントの DB 数 | 50,000(Paid)/ 10(Free) |
| 1 DB の最大サイズ | 10 GB(引き上げ不可) |
| 1 アカウントの総ストレージ | 1 TB(Paid)/ 5 GB(Free) |
| Time Travel 保持期間 | 30 日(Paid)/ 7 日(Free) |
| 1 Worker 呼び出しあたりのクエリ数 | 1,000(Paid)/ 50(Free) |
| 1 テーブルの最大カラム数 | 100 |
| 1 行の最大サイズ | 2 MB |
| SQL ステートメントの最大長 | 100 KB |
| 1 クエリのバインドパラメータ最大 | 100 |
| 単一クエリの最大実行時間 | 30 秒 |
| インポート最大ファイルサイズ | 5 GB |
| 1 Worker から D1 への同時接続 | 6 |
- 書き込みは単一の Durable Object に集約される。リードレプリカを増やしても書き込みスループットは上がらない。
- 書き込みプライマリのリージョンは選べない(自動配置)。書き込み元 Worker の地理位置によっては数十〜数百 ms のレイテンシが追加される。これがグローバル書き込みワークロードでは決定的な制約になる。
- 大量バッチは分割必須。100 万行の UPDATE は 1000 行 × 1000 バッチに割って実行する設計を初期から入れる。
- 過剰な並列リクエストはキューイングされ、キュー溢れで
overloadedエラーになる。リトライ設計が前提。 - ダッシュボードや Wrangler から実行したクエリも課金対象。
参考リンク
- 公式 Overview: https://developers.cloudflare.com/d1/
- Limits: https://developers.cloudflare.com/d1/platform/limits/
- Pricing: https://developers.cloudflare.com/d1/platform/pricing/
- Workers Binding API: https://developers.cloudflare.com/d1/worker-api/
- Time Travel: https://developers.cloudflare.com/d1/reference/time-travel/
- Read Replication: https://developers.cloudflare.com/d1/best-practices/read-replication/
参照日: 2026-05-03