Webアプリケーションを開発していると、「データベースをどこに置くか」という問題に必ず直面します。クラウド上のRDBMSを契約すれば機能的には十分ですが、月額費用がかさみ、リージョンの選定やコネクションプールの管理など、アプリケーションの本質とは無関係な作業に時間を取られることも少なくありません。特に中小規模のプロジェクトでは、インフラ管理の負担がそのまま開発速度のボトルネックになりがちです。

Cloudflare D1は、こうした課題に対する一つの回答として登場したサーバーレスデータベースです。SQLiteの構文がそのまま使え、Cloudflare Workersと組み合わせることで、エッジ環境でのデータ永続化を実現します。この記事では、D1の仕組みから実際の開発フロー、本番運用で得られた知見まで、実践的な視点でまとめています。

なぜ今、エッジデータベースが求められるのか

従来のデータベース構成が抱えるレイテンシの壁

Webアプリケーションの一般的な構成では、アプリケーションサーバーとデータベースサーバーが同じリージョン内に配置されます。ユーザーからのリクエストはCDNを経由して高速に届くものの、データベースへのクエリは特定のリージョンを往復するため、地理的に離れたユーザーほどレスポンスが遅くなります。

たとえば東京リージョンにデータベースを置いた場合、国内ユーザーへのレスポンスは良好ですが、北米やヨーロッパからのアクセスでは数十ミリ秒から百ミリ秒単位の遅延が加わります。グローバル展開を視野に入れたサービスでは、この遅延を解消するためにリードレプリカの配置やキャッシュ層の追加といった複雑なアーキテクチャが必要になり、小規模チームにとっては現実的ではありません。

D1が解決するサーバーレスデータベースの課題

D1は、Cloudflareのエッジネットワーク上で動作するSQLiteベースのデータベースです。従来のサーバーレスデータベースサービスと異なり、D1はWorkersのランタイムと同じ基盤で動作するため、アプリケーションコードからデータベースへのアクセスにネットワークの往復が発生しません。

この「ゼロレイテンシ接続」がD1の最大の特徴であり、従来のデータベース構成では実現が難しかったエッジでの高速データアクセスを可能にします。

サーバーの起動待ちも、コネクションプールの設定も不要です。Workersのコードから直接バインディング経由でクエリを発行するだけで、データの読み書きが完結します。無料プランでもデータベースを10個まで作成でき、1データベースあたり500MBのストレージが利用可能なので、プロトタイプから小〜中規模の本番環境まで幅広く対応できます。

Cloudflare D1の仕組みと特徴

SQLite互換のエッジネイティブDB

D1の内部エンジンはSQLiteです。SQLiteの公式ドキュメントに記載されているSQL構文がほぼそのまま使えるため、新たなクエリ言語を学ぶ必要がありません。CREATE TABLE、INSERT、SELECT、JOINといった標準的なSQLはもちろん、JSON関数やウィンドウ関数など、SQLiteの高度な機能も利用可能です。

ただし、PostgreSQLやMySQLのような拡張機能(ストアドプロシージャ、トリガーの一部、ユーザー定義関数など)はサポートされていません。D1はあくまで「SQLiteの範囲内でできること」に特化しており、その制約を理解した上で採用することが重要です。逆に言えば、SQLiteで十分な用途であれば、D1はインフラの管理コストをほぼゼロにしてくれる強力な選択肢になります。

Workersバインディングによるゼロレイテンシ接続

D1への接続は、Workersのバインディングという仕組みを通じて行います。従来のデータベース接続のように接続文字列を指定してTCPコネクションを張るのではなく、wrangler.tomlにバインディングを宣言するだけで、Workersのランタイム内から直接アクセスできるようになります。

wrangler.tomlでの設定は次のようにシンプルです。

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Workers内では env.DB を通じてクエリを実行します。

const result = await env.DB.prepare(
  "SELECT * FROM users WHERE id = ?"
).bind(userId).first();

この方式には、セキュリティ上の利点もあります。データベースの認証情報がコード内に露出せず、バインディング経由でのみアクセスが許可されるため、接続文字列の漏洩リスクがありません。

タイムトラベル機能によるデータ保護

D1にはタイムトラベルと呼ばれるバックアップ・復元機能が組み込まれています。これは過去30日間の任意の時点にデータベースを巻き戻せる機能で、手動でバックアップを取る必要がありません。

誤ってデータを削除してしまった場合や、マイグレーションが意図しない結果を引き起こした場合でも、Wranglerのコマンド一つでデータベースを特定の時点に復元できます。

npx wrangler d1 time-travel restore my-database --timestamp=2026-03-25T10:00:00Z

タイムトラベルは自動的にブックマークを作成し続けるため、「バックアップを取り忘れた」という事態が構造的に発生しません。この仕組みは、特にチーム開発でデータベースを共有している場合に大きな安心感をもたらしてくれます。

D1のセットアップからCRUD操作まで

Wranglerによるデータベース作成とマイグレーション

D1のセットアップはWrangler CLIを使って進めます。データベースの作成からテーブル定義まで、すべてコマンドラインで完結します。


npx wrangler d1 create my-app-db


npx wrangler d1 migrations create my-app-db create_users_table

マイグレーションファイルにSQLを記述し、適用します。

CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE INDEX idx_users_email ON users(email);

npx wrangler d1 migrations apply my-app-db --local


npx wrangler d1 migrations apply my-app-db --remote

マイグレーションの管理は、データベースの進化を追跡する上で欠かせません。D1のマイグレーション機能はシンプルですが、適用済みのマイグレーションを自動追跡してくれるため、チームメンバー間でのスキーマの不整合を防げます。

HonoフレームワークでREST APIを構築する

D1とHonoフレームワークを組み合わせることで、型安全なAPIを効率よく構築できます。Honoの公式ドキュメントにもCloudflare Workersとの統合方法が詳しく記載されています。

まず、TypeScriptでバインディングの型を定義します。

type Bindings = {
  DB: D1Database;
};

const app = new Hono<{ Bindings: Bindings }>();

CRUD操作を実装する際は、プリペアドステートメントを活用してSQLインジェクションを防ぎます。

// ユーザー一覧の取得
app.get("/api/users", async (c) => {
  const { results } = await c.env.DB.prepare(
    "SELECT id, name, email FROM users ORDER BY created_at DESC"
  ).all();
  return c.json(results);
});

// ユーザーの作成
app.post("/api/users", async (c) => {
  const { name, email } = await c.req.json();
  const result = await c.env.DB.prepare(
    "INSERT INTO users (name, email) VALUES (?, ?)"
  ).bind(name, email).run();
  return c.json({ id: result.meta.last_row_id }, 201);
});

// ユーザーの更新
app.put("/api/users/:id", async (c) => {
  const id = c.req.param("id");
  const { name, email } = await c.req.json();
  await c.env.DB.prepare(
    "UPDATE users SET name = ?, email = ?, updated_at = datetime('now') WHERE id = ?"
  ).bind(name, email, id).run();
  return c.json({ success: true });
});

Honoの軽量さとD1のシンプルさは相性がよく、数十行のコードで実用的なREST APIが完成します。

ローカル開発とリモート実行の切り替え

D1はローカル開発とリモート(本番)環境の切り替えが容易に設計されています。wrangler dev コマンドでローカル開発サーバーを起動すると、D1はローカルのSQLiteファイルとして動作します。


npx wrangler dev


npx wrangler d1 execute my-app-db --local --command "SELECT * FROM users;"


npx wrangler d1 execute my-app-db --remote --command "SELECT * FROM users;"

--local--remote フラグの切り替えだけで、開発中のデータを本番環境に影響を与えることなくテストできます。この仕組みにより、ローカルで自由にテストデータを投入してクエリのパフォーマンスを検証し、問題がなければリモートに適用するという安全な開発フローが実現します。

本番運用で押さえておくべきD1の設計パターン

スキーマ設計のベストプラクティス

D1はSQLiteベースであるため、データ型の扱いがPostgreSQLやMySQLとは異なります。SQLiteの型アフィニティを理解し、適切なスキーマを設計することが安定運用の鍵になります。

実務で特に気をつけるべき点をいくつか挙げます。日付型については、SQLiteにはネイティブのDATETIME型がないため、TEXT型で datetime('now') を使って保存するのが標準的なアプローチです。真偽値はINTEGERの0/1で表現し、JSONデータを格納する場合はTEXT型にJSON文字列を保存してSQLiteのJSON関数で操作します。

また、D1のデータベースサイズ上限は1データベースあたり10GBです。無料プランでは500MBまでとなるため、画像やファイルなどの大きなバイナリデータはR2に保存し、D1にはメタデータのみを格納する設計がよいでしょう。

パフォーマンスを左右するインデックス戦略

D1は個々のデータベースがシングルスレッドで動作するため、クエリのパフォーマンスがスループットに直結します。D1の制限に関する公式ドキュメントによれば、平均クエリ時間が1ミリ秒であれば毎秒約1,000クエリの処理が可能です。

パフォーマンスを維持するために、以下の点を意識してインデックスを設計します。WHERE句やJOIN条件で頻繁に使用するカラムにはインデックスを作成してください。ただし、書き込みが多いテーブルではインデックスの数を必要最小限に抑える必要があります。インデックスが増えるとINSERTやUPDATEの速度が低下するためです。

複合インデックスの設計では、カラムの順序が検索パフォーマンスに大きく影響します。選択性が高い(ユニークな値が多い)カラムを先頭に配置することで、インデックスの効率が向上します。

-- 良い例:選択性の高いカラムを先頭に
CREATE INDEX idx_articles_portal_status ON portal_articles(portal_code, status, published_at);

-- EXPLAINでクエリプランを確認する習慣をつける
EXPLAIN QUERY PLAN SELECT * FROM portal_articles
WHERE portal_code = 'DEV' AND status = 'published'
ORDER BY published_at DESC;

障害復旧とタイムトラベルの実践的な活用

先述のタイムトラベル機能は、障害復旧の場面で真価を発揮します。データの復元手順を事前にチームで共有しておくことで、障害発生時の対応速度が大きく変わります。

復旧の基本的な流れは次のとおりです。まず、wrangler d1 time-travel info コマンドで復元可能なブックマークを確認します。次に、障害が発生する直前の時点を特定し、wrangler d1 time-travel restore で復元を実行します。復元後は、データの整合性を確認するためにSELECTクエリで主要テーブルのレコード数や最新レコードをチェックします。

重要なのは、復元操作そのものもブックマークとして記録されるという点です。つまり、復元した結果が期待と異なっていた場合でも、さらに別の時点に巻き戻すことが可能です。この多層的なセーフティネットが、D1を本番環境で使う上での安心材料になります。

京谷商会がD1で運用している実例

全社ポータルサイトのコンテンツ管理基盤

京谷商会では、自社が運営する複数のポータルサイトのコンテンツ管理にD1を採用しています。Web開発、SEO、広告運用、データ分析など、各専門分野ごとのポータルに掲載される記事データを、すべて1つのD1データベースに集約しています。

当初は各ポータルごとに個別のデータベースを用意する案もありましたが、記事テーブルに portal_code カラムを持たせることで、1つのD1で全ポータルを横断的に管理する設計を選択しました。この設計により、ポータル間で用語集を共有したり、サイト全体の記事数やカテゴリ分布を一括で集計したりといった運用が容易になっています。

-- 全ポータルの記事数を一発で確認
SELECT portal_code, COUNT(*) as article_count
FROM portal_articles
WHERE status = 'published'
GROUP BY portal_code;

GTDタスク管理とSlack連携の裏側

京谷商会のタスク管理もD1上で動いています。GTD(Getting Things Done)メソッドに基づくタスクテーブルとプロジェクトテーブルを設計し、Cloudflare Workers経由でSlackボットからタスクの登録・更新・照会を行う仕組みです。

D1の低レイテンシのおかげで、Slackからのコマンドに対して体感的にはほぼ即座にレスポンスが返ります。従来のRDBMS構成では、Slackのインタラクションタイムアウト(3秒)に収めるためにバックグラウンドジョブを使う必要がありましたが、D1ではシンプルな同期処理で十分間に合います。

タスクの状態変更、担当者のアサイン、期日の設定といった操作が、すべてD1へのシンプルなSQL文として表現されている点もメンテナンスのしやすさにつながっています。ORMを使わず、プリペアドステートメントによる直接的なSQL実行を採用したことで、クエリの実行計画を常に把握できる透明性を確保しました。

10ポータル・数百記事を1つのD1で統合管理する設計

現在、京谷商会のD1には10を超えるポータルコードが登録され、数百件の記事データが格納されています。データ量としてはD1の上限(無料プランで500MB、有料プランで10GB)から見れば十分に余裕がありますが、実運用では「データ量」よりも「クエリパターンの最適化」が重要でした。

頻繁に実行されるクエリ、たとえば「特定ポータルの公開済み記事を新しい順に取得する」というパターンに対しては、複合インデックスを作成して対応しています。また、用語集テーブル(portal_glossary)との結合クエリが記事表示のたびに発生するため、用語集のslugにもインデックスを設定しています。

D1のシングルスレッド特性を踏まえると、「1つの重いクエリが全体のスループットを下げる」というリスクを常に意識する必要があります。京谷商会では、新しいクエリを追加する際に必ず EXPLAIN QUERY PLAN で実行計画を確認し、フルテーブルスキャンが発生していないことを検証するルールを設けています。

D1を選ぶべきケースと避けるべきケース

D1が最適なユースケース

D1が力を発揮するのは、読み取りが中心で、データ量が中規模(数GB以下)のアプリケーションです。ブログやポータルサイトのコンテンツ管理、ユーザー設定の保存、フォームの送信データの記録、APIのレート制限カウンターなど、Webアプリケーションの多くの一般的なユースケースに適合します。

特に、Cloudflare WorkersとHonoで構築するAPIのバックエンドデータストアとしては、セットアップの手軽さ、運用コストの低さ、タイムトラベルによる安心感の三拍子が揃っており、第一候補として検討する価値があります。D1の料金体系はクエリ数とストレージ量に基づくシンプルな従量課金で、データ転送料は発生しないため、コストの予測も立てやすいです。

他のデータストアを検討すべき場面

一方で、D1が適さないケースも明確に存在します。リアルタイム性の高い双方向通信(チャットアプリなど)には、Durable Objectsの方が適しています。大量の書き込みが同時に発生するワークロードでは、シングルスレッドの制約がボトルネックになり得ます。

また、10GBを超えるデータを1つのデータベースで扱う必要がある場合は、D1の設計思想(水平分割で複数の小さなデータベースに分散する)に沿うか、PlanetScaleやNeonといった別のサーバーレスデータベースを検討してください。全文検索が主要な要件である場合は、D1単体では力不足なので、Algoliaなどの専用サービスとの併用を考えることになります。

技術選定において大切なのは、D1の強みと制約の両方を正しく把握した上で、プロジェクトの要件と照らし合わせることです。万能なデータベースは存在しませんが、D1はその適用範囲内において、開発者体験とコスト効率の両面で優れた選択肢であることは間違いありません。

まとめ

Cloudflare D1は、エッジで動くSQLiteベースのサーバーレスデータベースとして、Webアプリケーション開発の選択肢を広げてくれるサービスです。Workersバインディングによるゼロレイテンシ接続、Wranglerを使ったシンプルなセットアップ、タイムトラベルによる安心のデータ保護、そしてSQLiteの枯れたSQL構文がそのまま使える親しみやすさが、D1の魅力といえます。

京谷商会では実際にD1を全社コンテンツ基盤として活用しており、10を超えるポータルの記事管理からGTDタスク管理まで、日常業務の根幹を支えています。中小規模のプロジェクトであれば、D1は従来のデータベース運用の煩雑さから解放してくれる選択肢として、検討する価値が十分にあります。まずはD1の公式ドキュメント(developers.cloudflare.com/d1/get-started/)を参照しながら、Wranglerで1つデータベースを作成してみてください。SQLiteに慣れていれば、思った以上にスムーズに本番投入まで進められるはずです。