本文へスキップ

純正のPostgreSQLやMySQLのないCloudflareでSQLデータをどこに置くか

純正のPostgreSQLやMySQLのないCloudflareでSQLデータをどこに置くか

執筆者

ECU Tech Blog編集部

はじめに

Cloudflare をプロジェクトで採用するなかで、データベースの選定を最初に決める必要がありました。Cloudflare のストレージには KV・R2・Vectorize など用途別の製品が揃っていますが(公式の整理)、本記事はアプリケーションの主データ、SQL で扱うリレーショナルなデータの保存先に絞ります。

2026年8月時点で、Cloudflare が自社でホストするマネージドな PostgreSQL や MySQL はありません。標準のデータベースは SQLite ベースの D1 で、PostgreSQL や MySQL を使いたければ外部に置き、Hyperdrive 経由で接続します。

先に実感を書くと、PoC や社内アプリでは D1 で足りる場面が意外と多いというのが正直なところです。一方で、既存の PostgreSQL 資産に接続したい要件や、読んだ結果を見てから次の書き込みを決める対話型のトランザクションを使う処理では、D1 の外に出る場面がありました。

最適な構成は要件次第で変わりますが、本記事は選定時に調べた特性と、判断に使った考え方を紹介します。

選択肢は D1・Durable Objects・外部データベース(PostgreSQL、MySQL)の3つ

主データの置き場所は、大きく次の3つです。

D1

Cloudflare 標準の SQL データベースです。SQLite ベースで、上限は1データベースあたり 10GB(有料プラン。公式の制限一覧)。「小さいデータベースを多数並べて水平にスケールさせる」のが公式の設計思想で、テナントやエンティティ単位の分割が想定されています。読み取りはグローバルリードレプリケーションで世界各地のレプリカに分散できます。Sessions API を使うとセッション内の逐次一貫性を保ったまま読み取りを近くのレプリカに逃がせて、書き込みは常にプライマリが受けます。

Durable Objects

ストレージ付きの実行単位です。オブジェクトごとに SQLite を内蔵し(有料プランで 10GB/オブジェクト)、単一スレッドで実行され、ストレージ操作には競合を防ぐ仕組みが組み込まれています(公式ドキュメント)。ただし、外部 API 呼び出しなど非ストレージの I/O を待つ間は、別のリクエストが割り込むことがあります。なお D1 自体、1つの Durable Object の上に作られています。

外部 PostgreSQL / MySQL 系 + Hyperdrive

PostgreSQL や MySQL を Cloudflare の外(Neon、Supabase、RDS など)に置き、Hyperdrive 経由で Workers から接続する構成です。Hyperdrive は PostgreSQL(9.0〜17.x)と MySQL(5.7〜8.x)を正式にサポートしています。Aurora・CockroachDB・MariaDB などプロトコル互換のデータベースにも対応します(SQL Server や MongoDB は非対応。対応一覧)。DB の近くに接続プールを置き、接続確立のコスト削減と読み取りキャッシュを担います。2026年6月18日からは、PlanetScale の Postgres を Cloudflare ダッシュボードから作成し、課金も Cloudflare に統合できるようになりました(リージョン選択可)。純正ではありませんが、接続の実体はこの構成と同じです。

判断の流れ

私たちが考える順序は次のとおりです(要件によって変わります)。D1 を既定にして、要件に応じて Durable Objects・外部データベースへ出します。それぞれの根拠は、このあとの節で補足します。

  1. 普通のリレーショナルデータで、書き込みを単一の SQL や batch() に畳めるか? — 畳めるなら D1 を既定に
  2. 読んだ結果で分岐して書く多段ロジックや、WebSocket・時限処理(Alarms)を使いたいか? — 整合性の範囲が1つの Durable Object に収まるなら Durable Objects
  3. 対話型トランザクション・既存の PostgreSQL / MySQL 資産・DB 固有機能・分割できない 10GB 超・BI 直接続はあるか? — あれば外部データベース + Hyperdrive
  4. 外部データベースで、往復の多い処理か? — 多いなら、1往復に畳む工夫と Placement の指定を検討
  5. 常駐プロセスなど Workers の制限を超える処理はあるか? — あれば、その部分だけ実行環境(Cloudflare Containers 等)を別に検討
向いている条件特性気にしておきたい制約
D1書き込みを事前に組める。読み取り中心。テナント分割できる。アドホックなクエリ・集計DB もプールも Cloudflare 内で完結する。小規模なら低コスト。読み取りレプリカをグローバルに配置できる(Sessions API)対話型トランザクション不可。データベースごとに書き込みは直列(クエリが速いほどスループットが出る構造)。10GB/DB(有料プラン)
Durable Objects整合性が1オブジェクトに収まる(在庫、予約枠、セッション、エージェントの状態)単一スレッド + ストレージの競合防止で、読んで分岐して書く処理を安全に書けるオブジェクト横断のトランザクション不可。複数オブジェクトをまたぐクエリ・集計が苦手。1オブジェクトあたりのスループットに上限
外部 PostgreSQL / MySQL 系 + Hyperdrive対話型トランザクション。既存の DB 資産・DB 固有機能・BI 直接続。分割できない 10GB 超PostgreSQL / MySQL の機能・エコシステムを広く使える(Hyperdrive 非対応の機能あり)トランザクションは接続を占有するため短く保つ。書き込み・キャッシュ外の読み取りは往復が残る(Placement で緩和)

まず D1 で足りるかを考える

普通のリレーショナルデータなら、まず D1 で組めるかを考えます。1つのデータベースの中なら、テーブルをまたぐ JOIN・集計・アドホックなクエリを普通に扱えます(公式も SQL でデータを横断的に調べる用途に適すると整理しています)。書き込みも、事前に SQL を組めるなら問題ありません。複数の SQL をまとめて原子的に実行するには batch() を使います(失敗すれば全体が取り消されます)。

一方で、D1 はオートコミットで動き、読んだ結果を見てから次の SQL を決める、という一連の流れを1つのトランザクションにする手段はありません。複数の D1 データベースや Durable Object をまたぐクエリも苦手です。調べた範囲でも実感としても、この対話型トランザクションの扱いが選定に最初に効いてきました。

もっとも、対話型の処理が出てきたら即座に外部データベース、とはなりません。整合性が必要な範囲はどこまでか、で切り分けます。Durable Objects は単一スレッドで動き、ストレージの読み書きは割り込まれません。「商品Aの在庫を読み、残っていれば1減らす」処理で怖いのは、2人が同時に最後の1個を読む競合ですが、商品Aを1つのオブジェクトにすれば、読み書きの間に他の処理が挟まらないため競合が起きません。商品Aのように、競合しうる読み書きを1つのオブジェクトに閉じ込めている限り、アプリ側で明示的なロックやトランザクション制御を書かずに済みます。ストレージ自体はトランザクショナルで強い一貫性を持ち、競合を防いでいるのは input/output gate というランタイムの仕組みです(SQLite ストレージ API)。公式も、調整が必要な単位ごとにオブジェクトを細かく分けることを推奨しています(Rules of Durable Objects)。

この在庫の例なら、条件付きの UPDATE 1文で D1 でも守れます(単一の文は D1 でも原子的です)。私たちは、両方で書けるなら D1 を既定にして、Durable Objects は1文に畳めない多段ロジックや WebSocket・時限処理(Alarms)を使うときに選んでいます。

口座間の振替のように整合性が複数のエンティティをまたぐ場合も、1つの D1 データベース内で単一の SQL や batch() に畳めるなら D1 で守れます。Durable Objects ではオブジェクト横断のトランザクションは組めません(1つのオブジェクトに複数のエンティティをまとめる手もありますが、処理がそこに集中します)。読んだ結果で分岐しながら書く流れがどうしても残るなら、外部データベースの出番です。既存の PostgreSQL / MySQL 資産・使い慣れた ORM・BI ツールとの直接続・D1 にない DB 固有機能・分割できない 10GB 超のデータも、外部データベースを選ぶ理由になります。ただし、Workers から外部データベースを使う場合はもう一つ、DB までの距離という問題が残ります。

外部データベースまでの距離と往復回数

応答時間は、おおむね「ユーザー→Worker」と「Worker→DB × 逐次実行するキャッシュ外のクエリ数」の合計になります。Worker は通常ユーザーの近くで動くため、DB から遠いユーザーがクエリの多い処理を呼ぶと、後半の往復が回数分積み上がります。

Hyperdrive は接続確立の高速化・接続プール・読み取りキャッシュで応答を縮めますが、キャッシュにないクエリの DB までの往復自体がなくなるわけではありません。書き込みやキャッシュ外の読み取りでは、クエリのたびに DB との通信が残ります。読み取りキャッシュは書き込みでは消えないため、書いた直後に最新値が要る処理では無効化の設定も必要です。トランザクション中はプールの接続を1本占有するので、短く保ち、外部 API 呼び出しや重い処理を挟まないことが公式に案内されています。

往復を減らす手は2つありました。複数の更新を1つの SQL 文やストアドファンクションに畳んで1往復にする工夫と、Worker 自体を DB の近くで動かすことです。

後者では Placement を使います。Worker を、ユーザーではなく DB に近い拠点で動かす設定で、自動で判定する Smart Placement と、リージョンやホストの明示指定があります(動くのは Cloudflare の拠点で、指定リージョンの内側ではありません)。これで、遠い区間の往復はユーザーから Worker までの1回だけになります。

通常のエッジ実行と Placement 適用後の比較図。通常はユーザーの最寄り拠点で Worker が動き、DB までの遠い往復をクエリの回数だけ支払う。Placement 適用後は Worker が DB に近い拠点で動き、遠い区間はユーザーから Worker までの1回だけになる

(数字は実測ではなく、DB から遠いユーザーが5クエリを逐次実行する場合の説明用の仮定です)

実行環境の選択はデータベースとは別軸

ランタイムの自由度が必要な場合は、データの置き場所と関係なく、コンテナに出す意味が残ります。常駐プロセス、Workers の CPU・メモリ制限を超える処理、重いネイティブ依存は載せにくいためです。その場合は、2026年4月に GA になった Cloudflare Containers を含めて置き場所に選択肢があります。今回の選定では、レイテンシを理由に Workers の外へ出す判断はしませんでした。

まとめ

まず D1 で組めるかを考え、読んで分岐して書く処理は整合性の範囲が1つのオブジェクトに収まるかを見て、収まらない要件だけ外部データベースに出す。外部データベースを選んだら DB までの距離を、Workers に載らない処理だけ実行環境を、それぞれ別の軸で考える。今回の選定はこの流れに落ち着きました。

整合性が必要な範囲とトランザクションの形を最初に洗い出しておいたことで、置き場所の候補が早めに絞れました。選定の参考になれば幸いです。

SNSでシェア