月曜日, 5月 12, 2025
ホームニューステックニュースCloudflare D1とSQLite storage in Durable Objectの比較

Cloudflare D1とSQLite storage in Durable Objectの比較


CloudflareではSQLiteをベースとしたデータベースを扱うのに2つの方法があります。

  • Cloudflare D1
  • SQLite storage in Durable Object

前者は最初にCloudflare上でデータベースを扱うことが出来る製品として大きく注目を集めてリリースされたSQLiteベースのフルマネージドなデータベースサービスです。大きな特徴としてはSQLiteとしてデータベースを構築しますが、Cloudflareということでリージョンという概念がなく、グローバルにデータベースを扱うことが出来るという点です。
それから経過した後に、Durable Object上に直接SQLiteを配置して扱うような機能がDurable Objectで使用できるようになりました。後者はどちらかというとD1のフルマネージドな機能を取っ払って、SQLiteを直接使用できるようにしたような製品です。(たぶん内部的な実装をオープンにした感じかなという印象です)
さらには直近の2025年4月のDeveloper WeekでDurable Objectの無料枠が追加され、使用するハードルが下がったことから、D1とSQLite storage in Durable Objectの違いを改めて知っておいて損は無いと思います。

Cloudflare Workers上での使用方法の違い

まず使用方法から違いがあります。どちらかというとSQLite storage in Durable Objectの方が癖のある使い方をする印象です。簡単なデータを取得するコードだけ見ていきます。
最初にD1を使うコードから見ていきます。

wrangler.jsonc

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  ...
  "d1_databases": [
    {
      "binding": "DB",
      "database_id": "YOUR_D1_DATABASE_ID",
      "database_name": "your_database_name",
    }
  ],
}

src/index.ts

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request, env): PromiseResponse> {
    const { results } = await env.DB.prepare(
      "SELECT * FROM Users WHERE name = ?",
    )
      .bind("tom")
      .all();
    return Response.json(results);
  },
} satisfies ExportedHandlerEnv>;

Users というテーブルからnametomのデータを取得するコードです。DBという環境変数にD1を設定するにはwrangler.jsoncなどに記述することでこのように簡単に使用することが出来ます。
次にSQLite storage in Durable Objectを使うコードを見ていきます。

wrangler.jsonc

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  ...
  "durable_objects": {
    "bindings": [
      {
        "name": "YOUR_DURABLE_OBJECT_NAME",
        "class_name": "YourDurableObjectClassName"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["YourDurableObjectClassName"]
    }
  ]
}

src/index.ts

export interface Env {
  YOUR_DURABLE_OBJECT_NAME: DurableObjectNamespaceimport("./src/index").YourDurableObjectClassName>;
}

export class YourDurableObjectClassName extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    ctx.blockConcurrencyWhile(async () => {
      await this._migrate();
    });
  }

  getUsers() {
    const cursor = this.sql.exec(
      "SELECT * FROM Users WHERE name = ?",
      "tom",
    )

    return { results: cursor.toArray() }
  }

  async _migrate() {
    this.sql.exec(`CREATE TABLE IF NOT EXISTS "Users" (
id INTEGER PRIMARY KEY NOT NULL,
username TEXT NOT NULL,
email TEXT NOT NULL
);`
    );
  }
}

export default {
  async fetch(request, env): PromiseResponse> {
    const id = c.env.CHECK_SQLITE_DURABLE_OBJECT.idFromName("db");
    const stub = c.env.CHECK_SQLITE_DURABLE_OBJECT.get(id);

    const { results } = stub.getUsers();
    return Response.json(results);
  },
} satisfies ExportedHandlerEnv>;

パッと見た感じどうでしょうか?D1に比べると使用方法がガラッと変わるのわかると思います。SQLite storage in Durable ObjectにはDurableObjectというDurable Objectを使用するためのクラス宣言が出てきたりします。またD1と大きく違うのはfetch関数から直接DBを操作するようなDMLを直接実行することは出来ません。

src/index.ts

export class YourDurableObjectClassName extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;
  }

  get db() {
    return this.sql 
  }
}

export default {
  async fetch(request, env): PromiseResponse> {
    const id = c.env.CHECK_SQLITE_DURABLE_OBJECT.idFromName("db");
    const stub = c.env.CHECK_SQLITE_DURABLE_OBJECT.get(id);

    const { results } = stub.db.exec("SELECT * FROM Users"); 
  }
} satisfies ExportedHandlerEnv>;

必ずDurableObjectのクラス側にか実装することが出来ません。これはDurableObject自体が実行するCloudflare Workersとは別のもの(たぶんDurable ObjectもCloudflare Workersで実装されているのでしょう)として定義されて、Cloudflare WorkersとDurable Objectは内部的に通信してデータのやり取りを行うためです。
通信してデータのやり取りを行うのでDurable Objectの内部に保持しているSQLiteおよびその接続情報は外に出せず、あくまでDurable Object内部でSQLiteへの問い合わせ結果をやり取りするくらいしか出来ないようになっています。

運用の違い

実行方法の違い以外に運用に関する違いが大きくあります。「使用できる」と「運用できる」という差には大きな差がありますが、D1とSQLite storage in Durable Objectにはこの運用を見据えた際に大きな差が出てきます。

マイグレーション方法

データベースは一度定義すればその後はデータの増減のみ管理すればいいだけかというとそうではありません。アプリケーションが変化することでそれに耐えるべくデータベースの形も変化していきます。
D1にはwrangler d1 migrateというコマンドが存在し、データベースを変更するDDLを管理する機能があります。このコマンドを実行することでデフォルトではmigrates/というディレクトリの中のSQLファイルを実行してくれます。もちろんこのwrangler d1 migrateは実行したマイグレーションファイルは実行されず、実行されていないマイグレーションファイルだけを実行してくれるという機能も存在しています。これによりD1のデータベースの変更などは比較的容易に可能です。

https://developers.cloudflare.com/workers/wrangler/commands/#d1-migrations-apply

逆にSQLite storage in Durable ObjectにはD1のようなマイグレーションを行ってくれるCLIや機能は存在しません。ですので、自身でどうにかしてDurable ObjectにあるSQLiteのデータベースをマイグレーションする必要があります。
先ほどのサンプルコードでも出てきたのですが、DurableObjectのクラスを定義の中にこのような記述があったと思います。

export class YourDurableObjectClassName extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    ctx.blockConcurrencyWhile(async () => {
      await this._migrate(); 
    });
  }

  ...

  async _migrate() {
    this.sql.exec(`CREATE TABLE IF NOT EXISTS "Users" (
id INTEGER PRIMARY KEY NOT NULL,
username TEXT NOT NULL,
email TEXT NOT NULL
);`
    );
  }
}

blockConcurrencyWhileという処理の中で_migrate関数つまりはテーブルを作成するDDLを実行しています。これはこの初期化が完了するまでこのDurable Objectの使用を待つという意味です。なので先程のコードの例だとリクエストが来た段階でDurable Objectのインスタンスを作成していますが、その作成時にDurable Objectのインスタンス化する時つまりはコールドスタートする時にこのマイグレーションが動作することになります。
これはD1とは大きくことなり、いくつかのデメリットが含まれています。

  1. 実リクエストが来るまで、DBへのマイグレーションが実行されない
  2. インスタンス化されたものを使用続ける限り問題はないが、コールドスタートになった場合にマイグレーション分の実行時間が必要となる

D1は事前にマイグレーションするためにCloudflare Workersの実行コストとは直接関係ありません。しかし、SQLite storage in Durable Objectはそうではなく、Cloudflare Workersが実行されて初めてマイグレーションが行われます。マイグレーションの結果次第では新バージョンのアプリケーションをデプロイさせないということが少し難しいです。また、実行時にマイグレーションを行うので、Durable Objectのインスタンス化したものがなくなってしまえばまたインスタンス化するのですが、その際にマイグレーション処理が動作してしまいます。このマイグレーションという点ではSQLite storage in Durable Objectは非常に運用しづらい点となっています。

サポート機能の違い

D1にはwrangler d1 migrateコマンド以外にもマネージドデータベースと便利な機能がいくつは存在しています。例えばwrangler d1 exportでD1のSQLiteのデータを取得できたり、wrangler d1 time-travelによりタイムトラベル機能によってバックアップされたデータの復元などが容易に行えます。

https://developers.cloudflare.com/workers/wrangler/commands/#d1

また、D1には簡易的ですが、Cloudflareのコンソール上にデータベースの中を参照できる機能なども存在します。

このようにD1には必要最低限ですが、マネージドデータベースとして持っておいてほしい機能が存在しますが、SQLite storage in Durable ObjectにはCloudflareからはD1に似た機能は一切提供されません。ですので必要ならば自身で構築する必要があります。

速度の違い

D1は色々と速度に懸念があると言われ続けて来ました。そこで単純にDurable Objectに乗ったSQLiteならばその点はクリアになっているのではないかという想像から実際に測ってみました。
測るために使用したアプリケーションは次のリポジトリのコードを実行してみましたが、ここに記載する内容を以下の条件化で速度を見てみました。

https://github.com/chimame/check-d1-performance

  1. テーブルに対してDELETEを発行して、100件のINSERT文を1回としたSQLを1000回(10万件ほど)実行する時間
  2. テーブルに対してSELECT COUNT(id)のSQLを実行する時間
  3. 10万件あるテーブルからLIMIT 50でデータを取得するSQLを実行する時間
  4. 10万件あるテーブルからLIMIT 50でデータを取得するSQLを100回実行する時間
  5. インデックスの効いていないカラムにLIKE検索でSQLを実行する時間
  6. 単純にINSERTを1回実行する時間

まずは「テーブルに対してDELETEを発行して、100件のINSERT文を1回としたSQLを1000回(10万件ほど)実行する時間」の結果です。

D1 SQLite storage in Durable Object
1回目 45,992 ms 417 ms
2回目 45,609 ms 432 ms
3回目 45,477 ms 453 ms
4回目 43,087 ms 401 ms
5回目 41,138 ms 427 ms

見ての通り、凄まじい差が出ます。D1に対して約100倍の速度でSQLite storage in Durable Objectは処理をします。本当かと疑うレベルですが、実際に次のテーブルのデータ数を取得する処理で正常にデータ件数が返ってくることを確認しているので、合っているとは思います。
次にとても単純なSQLとして「テーブルに対してSELECT COUNT(id)のSQLを実行する時間」を見ます。

D1 SQLite storage in Durable Object
1回目 50 ms 29 ms
2回目 38 ms 26 ms
3回目 58 ms 21 ms
4回目 39 ms 23 ms
5回目 90 ms 26 ms

これを見ると読み込みでもやはりSQLite storage in Durable Objectの方が早いですが、先程の100倍という差ではなく、2倍程度早いという結果です。それでも2倍というのは脅威ですが。
次にカウントではなく、実際にデータを取得するSQLとして「10万件あるテーブルからLIMIT 50でデータを取得するSQLを実行する時間」を見ます。

D1 SQLite storage in Durable Object
1回目 55 ms 19 ms
2回目 37 ms 19 ms
3回目 33 ms 21 ms
4回目 50 ms 21 ms
5回目 40 ms 16 ms

D1はさきほどのカウントを取得する処理時間とさほど違いはありませんが、SQLite storage in Durable Object側は気持ち早くなっています。そのせいでD1との差は2.5倍程度まで広がっているようにみえます。
もう少し見るためにそのSQLを100回実行するSQLを発行して速度を見てみます。

D1 SQLite storage in Durable Object
1回目 3,580 ms 41 ms
2回目 3,709 ms 44 ms
3回目 3,165 ms 46 ms
4回目 3,457 ms 45 ms
5回目 3,128 ms 41 ms

これは驚愕の結果です。D1は実行回数が多くなるので比例とまではいきませんが、それなりに実行速度が積み上がります。ところがSQLite storage in Durable Objectに関してはほとんど処理時間が積み上がらず1回の実行の時と倍ほど時間が違うだけです。何がどうなっているか正直わかりません。
最後に読み込みのSQLとして「インデックスの効いていないカラムにLIKE検索でSQLを実行する時間」を見ます。

D1 SQLite storage in Durable Object
1回目 84 ms 30 ms
2回目 93 ms 33 ms
3回目 53 ms 33 ms
4回目 86 ms 38 ms
5回目 73 ms 35 ms

どちらも処理には少し時間が増えますが、それもでSQLite storage in Durable Objectの速度劣化は小さい方だというのがわかります。
最後に書き込みを見るために「単純にINSERTを1回実行する時間」を見ます。

D1 SQLite storage in Durable Object
1回目 55 ms 31 ms
2回目 49 ms 29 ms
3回目 44 ms 26 ms
4回目 43 ms 27 ms
5回目 40 ms 29 ms

読み込みよりは若干処理時間は増えます。が、初期のデータを大量に入れるSQLの実行結果と比較するとSQLite storage in Durable Objectは何がどうなっているのかさっぱりわからない速度です。
このようにSQLite storage in Durable ObjectはD1に比べ格段に速度があがります。やはりD1に比べて色々なものを取っ払った生のSQLiteが出ているからなんでしょうか。

まとめ

確かにSQLite storage in Durable Objectは速度に不満のあったD1に比べれば断然に早いです。しかし、D1のようなマネージドのデータベースにほしい機能などがありません。自身で運用できるならばSQLite storage in Durable Objectを使うのもありですが、現実的には少し難しいかもしれません。
D1も機能はある程度ありますが、速度というはコールドスタートに若干の難があるため、これもこれでどこでも両手をあげて採用というのは厳しいです。
どちらもデメリットが飲める特定の用途に使用するというのが今のところの着地点かなとは思います。

フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -

インモビ転職