言語を切り替える
テーマを切り替える

Cloudflare D1 データベース実践:SQLite エッジデータベースとグローバルレプリケーション

0.01 ミリ秒。

これは SQLite がローカルで 1 行のデータを読み取る時間です。同じクエリを Cloudflare D1 で実行すると約 0.5 ミリ秒、PostgreSQL でリージョンをまたぐアクセスは 1〜3 ミリ秒かかるかもしれません。あまり大きな差がないように見えますか? しかし、もしユーザーが東京にいて、データベースがバージニアにある場合、ネットワークの往復だけで 100 ミリ秒以上かかります。

昨年、グローバルデプロイのプロジェクトを進めていたとき、この問題に直面しました。従来のデータベースでは、高遅延を我慢するか、複雑な読み取り・書き込み分離を行う必要がありました。Cloudflare が 2025 Developer Week で D1 のグローバルレプリケーション機能を発表するまでは、事態は一変しませんでした。

この記事では、D1 がどのように SQLite をエッジに配置したのか、その一見複雑そうな概念——Durable Objects、Lamport タイムスタンプ、Sessions API——を実際に使うとどうなるのか、いつ選ぶべきでいつ避けるべきかについてお話しします。

一、D1 とは:エッジで動く SQLite データベース

簡単に言えば、D1 は Cloudflare が SQLite を自社のエッジネットワークに配置したもので、世界中 300 都市以上のノードでデータベースの読み書きができるようにしたものです。

しかし、単に「SQLite + CDN」だと思うなら、その野心を見くびっています。従来の SQLite には本番環境で使いにくい致命的な問題があります。単一ファイル保存で分散デプロイができない、組み込みの障害回復がない、書き込み操作がデータベース全体をロックする。D1 はこれらに対して再設計を行いました。

従来の SQLite との違い

まず、統合方法です。D1 は Cloudflare Workers 内で直接動作し、通常の関数を呼び出すようにデータベース操作ができます。

// wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxx-xxxx-xxxx"

// Worker 内でのクエリ
export default {
  async fetch(request, env) {
    const { results } = await env.DB.prepare(
      "SELECT * FROM users WHERE id = ?"
    ).bind(1).all();
    return Response.json(results);
  }
}

次に、Time Travel です。D1 はデータベースの履歴バージョンを自動的に保存し、任意の時点にロールバックできます——この機能は SQLite にとって贅沢なものです。無料アカウントは 30 日間保持、有料アカウントはより長く保持できます。

3 つ目はグローバルレプリケーション(2025 年の大型アップデート)。データベースのプライマリノードはあるリージョンにありますが、読み取りレプリカは世界中に自動的に同期されます。シンガポールのユーザーがアクセスすると、データはシンガポールのレプリカから近くで読み取られ、遅延は 3 桁から 1 桁に低下します。

ただし、制限もあります

D1 は万能ではありません。選択前に知っておくべき制限がいくつかあります。

単一データベース 10GB 上限。超過する場合は分割するか、他のソリューションを検討する必要があります。1 アカウントで最大 50,000 データベース——ほとんどのプロジェクトには十分ですが、「ユーザーごとに 1 データベース」というビジネスモデルの場合、計算が必要です。

単一ライターアーキテクチャ。同時に 1 つのノードしか書き込み操作を処理できないため、書き込みスループットに上限があります。実測では約 500-2000 writes/sec で、PostgreSQL の 10K-50K とは比較になりません。リアルタイム入札、ログパイプラインなど高頻度書き込みのビジネスの場合、D1 では耐えられない可能性があります。

順序一貫性、強一貫性ではない。これは後で詳しく説明しますが、簡単に言えば、書き込んだデータが次の秒で読み取れない可能性があるということです。ただし、Sessions API を正しく使えば、この問題は完璧に解決できます。

正直に言えば、D1 に最も適しているシーンは、読み取りが多く書き込みが少ない Web アプリケーションです。ほとんどのウェブサイトは 90% 以上が読み取り操作で、D1 のグローバル読み取りレプリケーションにより、これらのリクエストは最も近いノードから応答され、体験向上は確実です。

二、D1 アーキテクチャ解説:Durable Objects とグローバルレプリケーション

この章は少し技術的ですが、D1 を使いこなすには、これらの概念を避けて通れません。

Durable Objects:各データベースに 1 つの「執事」

D1 の中核は Durable Objects です。各データベースに専用の「執事プロセス」があると想像してください。この執事が担当するのは:

  1. グローバル一意性の保証:すべての書き込み操作が必ずここを通るため、2 人が同時に同じデータを修正して競合することはない
  2. トランザクションログの維持:すべての書き込みが記録され、障害回復とレプリカ同期に使用
  3. 読み取り・書き込みレプリカの調整:世界中のレプリカに「更新が必要」と通知

この設計は巧妙です。従来の分散データベースは複数のノード間で調整する必要があり、ネットワーク遅延やノード障害が様々な問題を引き起こします。D1 のアプローチは、1 つのプライマリノードを決め、すべての書き込み操作がそこに並んで順番に処理され、その後非同期でレプリカに同期されます。

Snapshot Isolation:読み取り操作がブロックされない

D1 で SELECT クエリを実行すると、プライマリノードに並ばず、最も近いレプリカから直接「スナップショット」を読み取ります。

どういうことか? データベースのプライマリノードが北京にあり、東京、シンガポール、シドニーにレプリカがあるとします。東京のユーザーが読み取りリクエストを発行すると、D1 は東京のレプリカにルーティングし、その時点のデータ状態を返します。このスナップショットはクエリ開始の瞬間に決定されるため、同じ時刻にプライマリノードが新しいデータを書き込んでいても、読み取り操作はブロックされません。

ただし、ここに問題があります。データを書き込んですぐ読み取ると、読み取れない可能性があります。レプリカにまだ同期されていないからです。

これが D1 が Sessions API を提供する理由です。

Lamport タイムスタンプ:順序に意味を持たせる

Leslie Lamport は 1978 年に分散システムのイベント順序付け方法を提案し、後に Lamport タイムスタンプと呼ばれました。中核となる考え方は単純です。すべてのイベントに論理クロックがあり、後続のイベントのタイムスタンプは必ず前のものより大きくなります。

D1 はこのメカニズムを使用して「順序一貫性」を保証します。あるセッション内で書き込んでから読み取ると、D1 は書き込み後の最新データを読み取ることを保証し、まだ同期されていない古いレプリカは読み取りません。

具体的にはどうするか? 書き込み完了後、D1 は「ブックマーク」(commit token)を返します。このブックマークはマーカーポイントのようなもので、「このポイントより前のすべての変更が有効になった」ことを示します。次回クエリ時にこのブックマークを付けると、D1 はデータが少なくともこのポイントより新しいことを保証します。

ユーザー → 注文を書き込み → commit token "abc123" を取得
ユーザー → 注文をクエリ(token "abc123" を含む)→ 書き込んだデータを確実に見る

グローバルレプリケーションの実装方法

D1 データベースを作成すると、「プライマリリージョン」(primary location)を選択します。デフォルトは最も近い Cloudflare データセンターで、手動で指定することもできます。

書き込みフロー:

  1. 書き込みリクエストが最も近いエッジノードに到達
  2. プライマリリージョンの Durable Object にルーティング
  3. プライマリデータベースファイルに書き込み
  4. 世界各地のリージョンのレプリカに非同期複製

読み取りフロー:

  1. 読み取りリクエストが最も近いエッジノードに到達
  2. そのリージョンのレプリカから読み取り
  3. セッション付きの読み取りの場合、一貫性ブックマークが有効であることを保証

Cloudflare 公式はグローバルレプリケーションに追加料金を取らないと言っています。これは良心的です。データ転送コストは本来大きいからです。ただし、書き込み操作は依然としてプライマリリージョンにルーティングされるため、遅延はプライマリリージョンとの物理距離に依存します。プライマリリージョンをアメリカに設定しても、ユーザーの大部分がアジアにいる場合、書き込み時に遅延を明確に感じるでしょう。

三、Sessions API 実践:順序一貫性のコード実装

理論は終わりました。コードの書き方を見ていきましょう。

Sessions API は D1 が 2025 年にリリースした新機能で、「書き込み後読み取り」一貫性問題を専門的に解決します。MongoDB の因果一貫性や CockroachDB の follower reads を使ったことがあれば、概念は似ています。何らかのマーカーで因果関係を追跡します。

最も基本的な使い方

// セッションを作成
const session = env.DB.withSession();

// 通常の読み取りクエリ、最も近いレプリカにルーティング
const { results } = await session.prepare(
  "SELECT * FROM products WHERE category = ?"
).bind("electronics").all();

// 書き込みクエリ、自動的にプライマリにルーティング
await session.prepare(
  "INSERT INTO orders (user_id, product_id, quantity) VALUES (?, ?, ?)"
).bind(userId, productId, 2).run();

// 現在のセッションの一貫性ブックマークを取得
const bookmark = session.latestCommitToken;

ここで重要なのは withSession() です。これが「セッションコンテキスト」を作成し、このコンテキスト内のすべての操作が同じ一貫性ビューを共有します。

3 つの一貫性モード

Sessions API は 3 つのモードを提供し、異なる使用シーンに対応します。

1. first-unconstrained(デフォルト)

const session = env.DB.withSession("first-unconstrained");

最も緩いモード。読み取り操作は最も近いレプリカから直接読み取り、このレプリカが最新かどうかを問いません。リアルタイム性がそれほど重要でないシーンに適しています。商品リスト、ブログ記事の表示など。

2. first-primary

const session = env.DB.withSession("first-primary");

最初の読み取り操作はプライマリにルーティングされ、その後の読み取り操作はレプリカを使用します。これにより、「書き込んだばかりのデータ」を見ることが保証されますが、毎回プライマリをクエリしたくないシーンに適しています。

3. ブックマークを使って前のセッションを継続

// リクエストヘッダーから前回のブックマークを取得
const previousToken = request.headers.get("x-d1-token") ?? "first-unconstrained";

// セッションを作成、前のセッションを継続
const session = env.DB.withSession(previousToken);

// 操作を実行...

// 新しいブックマークを返す
response.headers.set("x-d1-token", session.latestCommitToken);

これが最も強力な使い方です。ブックマークをクライアント(ブラウザの Cookie やリクエストヘッダーなど)に保存し、各リクエストに付けて、複数のリクエストにわたって一貫性を維持できます。

実践シーン:eコマース注文システム

グローバルな eコマースプラットフォームを作っているとします。ユーザーが商品を閲覧するときは、最も近いレプリカから読み取り、遅延を最小にしたい。しかし、ユーザーが注文した後、注文を表示するときは、必ず今入れた注文を見たい。

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // リクエストヘッダーから session token を取得(最初のリクエストは null)
    const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
    const session = env.DB.withSession(token);

    // ケース1:商品リストを閲覧(強一貫性は不要)
    if (url.pathname === "/api/products") {
      const { results } = await session.prepare(
        "SELECT * FROM products WHERE status = ?"
      ).bind("active").all();

      return new Response(JSON.stringify(results), {
        headers: {
          "Content-Type": "application/json",
          "x-d1-token": session.latestCommitToken
        }
      });
    }

    // ケース2:注文を作成(書き込み操作、自動的にプライマリにルーティング)
    if (url.pathname === "/api/orders" && request.method === "POST") {
      const body = await request.json();

      await session.prepare(`
        INSERT INTO orders (user_id, total_amount, status)
        VALUES (?, ?, ?)
      `).bind(body.userId, body.total, "pending").run();

      // 書き込み後すぐクエリ、書き込んだデータを確実に読み取る
      const order = await session.prepare(`
        SELECT * FROM orders WHERE user_id = ?
        ORDER BY created_at DESC LIMIT 1
      `).bind(body.userId).first();

      return new Response(JSON.stringify(order), {
        headers: {
          "Content-Type": "application/json",
          "x-d1-token": session.latestCommitToken  // 新しい token を返す
        }
      });
    }

    // ケース3:注文詳細を表示(前回の token を使用、一貫性を保証)
    if (url.pathname.startsWith("/api/orders/")) {
      const orderId = url.pathname.split("/")[3];

      // ユーザーが注文したばかりの場合、token は最新データを読み取ることを保証
      const order = await session.prepare(
        "SELECT * FROM orders WHERE id = ?"
      ).bind(orderId).first();

      return new Response(JSON.stringify(order), {
        headers: {
          "Content-Type": "application/json",
          "x-d1-token": session.latestCommitToken
        }
      });
    }
  }
}

この設計は実用的です。商品閲覧時は first-unconstrained を使い、パフォーマンスが最高です。注文後、クライアントが token を保存し、その後の注文表示時に付けることで、一貫性を保証します。

クライアント側の連携

フロントエンドがやるべきことは単純です。x-d1-token を保存し、各リクエストに付けること。

// フロントエンド例
let d1Token = localStorage.getItem('d1-token') ?? 'first-unconstrained';

async function fetchProducts() {
  const response = await fetch('/api/products', {
    headers: { 'x-d1-token': d1Token }
  });
  d1Token = response.headers.get('x-d1-token');
  localStorage.setItem('d1-token', d1Token);
  return response.json();
}

async function createOrder(data) {
  const response = await fetch('/api/orders', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-d1-token': d1Token
    },
    body: JSON.stringify(data)
  });
  d1Token = response.headers.get('x-d1-token');
  localStorage.setItem('d1-token', d1Token);
  return response.json();
}

ご覧のとおり、コード量は多くありませんが、解決する問題は大きいです。このメカニズムがないと、ユーザーが注文した直後に注文リストを見ると、空っぽに見える可能性があり、体験は最悪です。

四、パフォーマンスベンチマークと競合比較

データは嘘をつきません。ここでは主要なソリューションの比較を整理しました。データは公式ドキュメントとコミュニティの実測値から来ています。

遅延比較

ソリューション読み取り遅延 (p50)読み取り遅延 (p99)書き込み遅延 (p50)説明
D1~0.5ms~2-5ms~5-30ms読み取りはエッジレプリカ、書き込みはプライマリ
Turso~0.02ms~0.1ms~15-50ms組み込み読み取り、驚くほど高速
PlanetScale~3-8ms~10-20ms~3-8msMySQL 互換、読み書きともにプロキシ経由
PostgreSQL (Neon)~3-10ms~20-50ms~1-5ms従来アーキテクチャ、コールドスタート遅い
0.5ms
D1 読み取り遅延
p50、エッジレプリカ
0.02ms
Turso 読み取り遅延
組み込み読み取り
500-2K
D1 書き込みスループット
writes/sec
10GB
単一DB上限
超過時は分割必要
数据来源: 公式ドキュメントとコミュニティ実測

いくつかの観察:

Turso の読み取り遅延は本当に速い。0.02 ミリ秒は、基本的にローカルメモリアクセスの速度です。組み込み SQLite を使っているため、データベースファイルがエッジノードに直接コピーされ、読み取りは完全にローカル化されます。ただし、これには代償があります。データ同期には追加の処理が必要で、書き込み遅延は逆に高くなります。

D1 の読み取り遅延も優秀。0.5 ミリ秒はエッジデータベースとしてトップレベルです。ただし、書き込み遅延の差は明確です。書き込み操作はプライマリにルーティングされるため、物理距離が遅延の下限を決定します。プライマリが米国西海岸にあり、ユーザーがシンガポールにいる場合、書き込み操作は太平洋を渡る必要があり、30 ミリ秒は最低でもかかります。

PlanetScale と Neon は従来のアプリケーションに適している。遅延データは D1 や Turso ほど目立ちませんが、エコシステムが成熟し、機能が完全です。複雑な SQL 機能(ストアドプロシージャ、トリガー、豊富なインデックスタイプ)が必要な場合、この 2 つが適しています。

スループット比較

ソリューション読み取りスループット (QPS)書き込みスループット (QPS)説明
D110K-100K500-2K単一データベース制限
Turso無制限(ローカル読み取り)同期制限あり各エッジノードが独立読み取り
PlanetScale10K-50K5K-20Kシャーディングで拡張可能
PostgreSQL10K-100K10K-50Kインスタンスサイズに依存

D1 の書き込みスループットは弱点です。単一ライターアーキテクチャが上限を決定します。アプリケーションが毎秒 5000 行以上の書き込みを必要とする場合、D1 はボトルネックになる可能性があります。この場合、データベースを分割するか(ただし複雑さが増す)、他のソリューションに切り替える必要があります。

無料枠比較

ソリューションストレージ読み取り枠書き込み枠備考
D15GB250億行/月5000万行/月10GB/データベース上限
Turso9GB10億行/月2500万行/月レプリケーション通信含む
PlanetScale1GB100億行/月100億行/月書き込み制限なし
Neon0.5GB1億単位/月1億単位/月単位=読み取りまたは書き込み

無料枠から見ると、D1 はかなり太っ腹です。250 億行の読み取りは、個人プロジェクトや小規模アプリには十分です。ただし、D1 の書き込み制限に注意が必要です。5000 万行/月 は、平均して毎日 166 万行です。ログ収集、トラッキング報告など高頻度書き込みシーンでは、簡単に超過します。

課金モデル

D1 の課金は単純です。従量課金で、最低消費はありません。無料枠を超過後、100 万行の読み取りにつき $0.001、100 万行の書き込みにつき $0.10。ストレージは GB あたり $0.75/月。

Turso の課金は少し複雑で、「行読み取り」と「レプリケーション通信」の 2 つの次元に関係し、データが頻繁に更新されると、レプリケーションコストが高くなる可能性があります。

PlanetScale の課金は「行読み取り」と「行書き込み」に基づき、書き込みコストは D1 より低いですが、読み取りコストはやや高いです。

私の提案:Cloudflare エコシステム(Workers、KV、R2)を深く使っている場合、D1 の課金統合で請求書がより明確になります。独立したプロジェクトの場合、3 社とも試してみて、実際のデータで決めるのが良いでしょう。

五、選定判断ツリー:いつ D1 を選ぶべきか

これだけ話して、結局 D1 を選ぶべきか? 簡単な判断ツリーを描きました。

D1 に適したシーン

アプリケーションが読み取り集約型。コンテンツサイト、eコマースプラットフォーム(閲覧中心)、ブログ、ドキュメントシステムなど。こうしたアプリケーションは 90% 以上が読み取り操作で、D1 のグローバル読み取りレプリケーションは遅延を 1 桁ミリ秒に下げられます。

ユーザーが世界中に分布している。従来の単一リージョンデータベースでは、遠隔地のユーザーは各リクエストで海を渡る必要があります。D1 はデータを「ユーザーの近く」に移動させ、体験向上は劇的です。

すでに Cloudflare Workers を使っている。D1 と Workers の統合はネイティブで、数行のコードで設定できます。別途データベース接続プールは不要、コールドスタートの心配もなく、開発体験はスムーズです。

データベース規模が 10GB 以内。D1 の単一データベース制限は 10GB で、超過すると分割が必要です。ビジネスが「テナントごとに 1 データベース」という場合、この制限はむしろ関係ありません。

D1 に適さないシーン

高頻度書き込みアプリケーション。リアルタイム入札システム、ログパイプライン、IoT データ収集——これらのシーンでは毎秒数万回の書き込みが発生する可能性があり、D1 の単一ライターアーキテクチャはボトルネックになります。PostgreSQL、ClickHouse または TimescaleDB が適しています。

複雑なトランザクションが必要。D1 は現在 SQLite のトランザクションレベルをサポートしていますが、SERIALIZABLE 分離、クロスデータベーストランザクション、または複雑なストアドプロシージャが必要な場合、満たせません。

データ量が 10GB を超える。分割は可能ですが、運用の複雑さが増します。データが本質的に大きい場合(時系列データ、ログアーカイブなど)、最初から他のソリューションを選ぶのが良いでしょう。

強一貫性が必要。D1 は結果整合性システムで、書き込み後すぐに読み取ると最新データが見えない可能性があります(Sessions API を使わない限り)。ビジネスがいつでもどこでも最新データを読み取る必要がある場合、他のソリューションを検討する必要があります。

PostgreSQL からの移行時の注意点

既存の PostgreSQL アプリケーションを D1 に移行する場合、事前に考慮すべきことがいくつかあります。

1. SQL 方言の違い

SQLite は PostgreSQL の一部の機能をサポートしていません。

  • RETURNING 句がない(2 ステップ必要:挿入後にクエリ)
  • SERIAL 型がない(INTEGER PRIMARY KEY AUTOINCREMENT を使用)
  • JSONB 型がない(TEXT で JSON を保存、json_extract() 関数を使用)
  • ARRAY 型がない(関連テーブルが必要)

2. データ移行ツール

Cloudflare 公式は移行ツールを提供し、PostgreSQL からの SQL エクスポート、D1 へのインポートをサポートしています。

# PostgreSQL データをエクスポート
pg_dump --format=insert mydb > dump.sql

# D1 にインポート
npx wrangler d1 execute my-d1-database --file=dump.sql

ただし、複雑なスキーマは手動調整が必要な場合があります。

3. 接続方法の変化

従来のデータベースは永続接続ですが、D1 はステートレスな関数呼び出しです。ORM を調整するか、直接ネイティブ SQL を使う必要があります。Prisma は D1 アダプターがありますが、機能はまだ改善中です。

簡単な判断

まだ迷っている場合、この簡単な判断を試してみてください。

アプリケーションの書き込み頻度 > 1000 回/秒?
├─ はい → D1 を選ばない
└─ いいえ
    └─ 強一貫性が必要?
        ├─ はい → D1 を選ばない(または Sessions API と組み合わせ)
        └─ いいえ
            └─ データ量 > 10GB?
                ├─ はい → 慎重に検討
                └─ いいえ → D1 は適している

まとめ

D1 の中核的価値は 3 つの文で要約できます。エッジデプロイで遅延を 1 桁ミリ秒に下げ、サーバーレスアーキテクチャで運用を気にする必要がなく、Sessions API が分散システムで最も頭の痛い一貫性問題をシンプルに解決します。

ただし、これはすべてのシーンに適しているわけではありません。高頻度書き込み、複雑なトランザクション、超大規模データ——こうした場合、PostgreSQL と専用の時系列データベースが依然として適しています。選択に銀の弾丸はなく、トレードオフだけです。

グローバルユーザー、読み取り多く書き込み少ない Web アプリケーションを作っていて、すでに Cloudflare Workers を使っている場合、D1 は試す価値があります。テストデータベースの作成は数分で済みます。

# データベースを作成
npx wrangler d1 create my-first-db

# テーブルを作成
npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"

# データを挿入
npx wrangler d1 execute my-first-db --command="INSERT INTO users (name) VALUES ('test')"

実際に動かしてみて、東京から米国西海岸のデータベースへの遅延の違いを感じれば、自分のプロジェクトに適しているかどうかわかります。


"D1 は Cloudflare の SQLite エッジデータベースで、グローバル読み取りレプリケーションとサーバーレス体験を提供します。その Sessions API は Lamport タイムスタンプを通じて順序一貫性を実現し、分散システムでよくある書き込み後読み取り一貫性問題を解決します。"

参考資料

Cloudflare D1 データベースクイックスタート

データベース作成から Sessions API 一貫性読み取りの実装までの完全な流れ

⏱️ 目安時間: 15 分

  1. 1

    ステップ1: D1 データベースを作成

    wrangler CLI を使用してデータベースを作成:

    ```bash
    npx wrangler d1 create my-first-db
    ```

    作成後、database_id が返されるので、wrangler.toml に設定:

    ```toml
    [[d1_databases]]
    binding = "DB"
    database_name = "my-first-db"
    database_id = "your-database-id"
    ```
  2. 2

    ステップ2: テーブルを作成

    SQL を実行してテーブル構造を作成:

    ```bash
    npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"
    ```

    SQL ファイルを使用して一括実行も可能:

    ```bash
    npx wrangler d1 execute my-first-db --file=./schema.sql
    ```
  3. 3

    ステップ3: Worker で Sessions API を使用

    セッション付きデータベース接続を作成し、書き込み後読み取り一貫性を実現:

    ```typescript
    export default {
    async fetch(request, env) {
    // リクエストヘッダーから session token を取得
    const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
    const session = env.DB.withSession(token);

    // データを書き込み
    await session.prepare("INSERT INTO users (name) VALUES (?)")
    .bind("test").run();

    // 読み取り時に一貫性を保証
    const { results } = await session.prepare("SELECT * FROM users")
    .all();

    return new Response(JSON.stringify(results), {
    headers: { "x-d1-token": session.latestCommitToken }
    });
    }
    }
    ```
  4. 4

    ステップ4: グローバル読み取りレプリケーションを設定

    wrangler.toml でプライマリリージョンを指定:

    ```toml
    [[d1_databases]]
    binding = "DB"
    database_name = "my-first-db"
    database_id = "your-database-id"
    primary_location_hint = "apne1" # 東京リージョン
    ```

    利用可能なリージョンコード:
    - apne1: 東京
    - sfo1: サンフランシスコ
    - eur3: フランクフルト

FAQ

Cloudflare D1 と Turso の違いは何ですか?
両方ともエッジ SQLite データベースですが、アーキテクチャが異なります:

• D1:単一ライターアーキテクチャ、書き込み操作はプライマリにルーティング、読み取り遅延約 0.5ms、読み取り多く書き込み少ないシーンに適している
• Turso:組み込み読み取り、遅延はさらに低い(約 0.02ms)、データ同期はより複雑

D1 の利点は Cloudflare Workers とのネイティブ統合、無料枠が大きい(250 億行読み取り/月);Turso の利点は読み取りパフォーマンスが極限、遅延に敏感なシーンに適している。
D1 の 10GB 単一データベース制限をどう突破するか?
3 つのソリューション:

• データベース分割戦略:ビジネスモジュールごとに分割、各モジュールに 1 つのデータベース
• テナント分離:各テナントに 1 つのデータベース、D1 は最大 50,000 データベースをサポート
• ハイブリッドストレージ:ホットデータは D1、コールドデータは R2 または他のオブジェクトストレージに移行

データ量が成長し続け 10GB を超える場合、PlanetScale や従来の PostgreSQL など他のソリューションの検討を推奨。
Sessions API の 3 つのモードをどう選ぶか?
ビジネスシーンに応じて選択:

• first-unconstrained(デフォルト):商品リスト、ブログ表示などリアルタイム性がそれほど重要でないシーンに適している
• first-primary:「書き込んだばかりのデータ」を見る必要があるが、毎回プライマリをクエリしたくないシーンに適している
• commit token モード:eコマース注文、注文表示など複数リクエストにわたる一貫性が必要なシーンに適している

eコマースシステムの推奨:閲覧時は first-unconstrained、注文後 token を保存、以降のリクエストに付ける。
D1 は高頻度書き込みシーンに適しているか?
あまり適していません。D1 の単一ライターアーキテクチャにより、書き込みスループット上限は約 500-2000 writes/sec で、PostgreSQL の 10K-50K よりかなり低いです。

アプリケーションに以下の特徴がある場合、他のソリューションの選択を推奨:
• リアルタイム入札システム
• ログパイプライン、トラッキング報告
• IoT データ収集
• 毎秒 1000 回以上の書き込み

これらのシーンには PostgreSQL、ClickHouse または TimescaleDB がより適している。
PostgreSQL から D1 に移行する際の注意点は?
主な違い:

• SQL 方言:SQLite は RETURNING、SERIAL、JSONB、ARRAY 型をサポートしない
• 接続方法:永続接続からステートレス関数呼び出しに変更
• ORM 適応:Prisma は D1 アダプターがあるが、機能はまだ改善中

移行ステップ:
1. pg_dump を使用してデータをエクスポート
2. 非互換 SQL 構文を手動調整
3. wrangler d1 execute を使用してインポート

小規模データでテストし、機能正常を確認後に本格移行を推奨。
D1 のグローバルレプリケーションは追加料金がかかるか?
かかりません。Cloudflare 公式はグローバル読み取りレプリケーションに追加料金を取らないと明言、データ転送コストは課金に含まれる。

ただし注意:
• 書き込み操作は依然としてプライマリにルーティング、遅延はプライマリとの物理距離に依存
• プライマリがアメリカ、ユーザーがアジアにいる場合、書き込み遅延は約 30ms+
• 読み取り無料枠は高い(250 億行/月)、書き込み枠は 5000 万行/月

プライマリをユーザーが最も集中するリージョンにデプロイし、書き込み体験を最適化することを推奨。

9 min read · 公開日: 2026年5月5日 · 更新日: 2026年5月5日

関連記事

コメント

GitHubアカウントでログインしてコメントできます