Featured image of post Cloudflare WorkersとD1でサーバーレス構成を作る

Cloudflare WorkersとD1でサーバーレス構成を作る

目次

背景

  • Cloudflare WorkersとD1について調べたので、基本的な仕組みと、最小構成でのセットアップ手順を整理する

Cloudflare Workersとは

  • V8 isolate(ChromeのV8エンジンが使う、軽量な実行コンテキストの分離単位)をベースにしたサーバーレスの実行環境
  • コンテナやVMを起動しないため、コールドスタートが非常に速い
  • デプロイすると、Cloudflareのエッジロケーションに配信され、リクエスト元に地理的に近い場所で実行される
  • 基本的にJavaScript/TypeScript(またはWasm)で書く

AWS Lambdaとの違い

観点AWS LambdaCloudflare Workers
実行基盤Firecracker(軽量VM)V8 isolate
コールドスタート比較的遅い(ランタイムやメモリ設定による)非常に速い(isolateはVMより起動が軽い)
実行場所指定したAWSリージョンデフォルトでエッジ(リクエスト元に近い場所)
対応言語Node・Python・Java・Goなど幅広い基本的にJS/TS(Wasm経由で他言語も可)
実行時間最大15分短時間のリクエスト/レスポンス処理が主眼
周辺サービスS3・DynamoDB・SQSなどAWS一式D1・R2・KV・Durable Objectsなど

D1とは

  • D1は、SQLiteのSQLセマンティクスを持つ、Cloudflareのマネージド・サーバーレスデータベース
  • マネージド型のディザスタリカバリ機能を備え、Workerおよび HTTP API からアクセスできる

スケーリングモデル

  • 1つのデータベースを大きくする垂直スケーリングではなく、データベースを複数作ることで水平にスケールする設計
  • マルチテナントのアプリケーション(顧客ごとにDBを分離したい場合など)で、分離のための追加コストなしに数千のデータベースを扱える
  • 読み取り専用のレプリカをエッジに配置することで、地理的に近い場所から読み取れる

料金モデル

  • 料金は、クエリ数とストレージ量にのみ基づく。インフラの管理コストは発生しない

プロジェクトを作る

Workersプロジェクトの作成

  • create-cloudflare(C3)というCLIツールで、Workerプロジェクトをスキャフォールドできる
1
npm create cloudflare@latest -- my-first-worker
  • セットアップ時に、テンプレートとして"Hello World example"、プロジェクトタイプとして"Worker only"を選ぶ
  • デプロイはwrangler(Cloudflareの公式CLI)で行う
1
npx wrangler deploy
  • デフォルトでは<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.devというサブドメインに公開される

D1データベースの作成とバインディング

  • D1データベースも、wranglerから作成する
1
npx wrangler d1 create prod-d1-tutorial
  • コマンド実行時に、設定ファイルへのバインディング追加を聞かれるので、Yesを選ぶと、必要な設定がそのまま出力される
  • wrangler.jsonc(またはwrangler.toml)に、以下のようにバインディングを追加する
1
2
3
4
5
6
7
8
9
{
  "d1_databases": [
    {
      "binding": "prod_d1_tutorial",
      "database_name": "prod-d1-tutorial",
      "database_id": "<データベースのID>"
    }
  ]
}
  • このバインディング名は、Workerのコードからenv.<バインディング名>という形でアクセスできる、有効なJavaScriptの変数名である必要がある

Workerからのクエリ

  • 実際のWorkerコードでは、env.<バインディング名>.prepare()でSQLを準備し、bind()でプレースホルダに値を安全に埋め込み、run()で実行する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
export interface Env {
  prod_d1_tutorial: D1Database;
}

export default {
  async fetch(request, env): Promise<Response> {
    const { pathname } = new URL(request.url);
    if (pathname === "/api/beverages") {
      const { results } = await env.prod_d1_tutorial.prepare(
        "SELECT * FROM Customers WHERE CompanyName = ?"
      )
        .bind("Bs Beverages")
        .run();
      return Response.json(results);
    }
    return new Response("Call /api/beverages...");
  },
} satisfies ExportedHandler<Env>;
  • プレースホルダ(?)とbind()を使う形になっているため、SQLインジェクションを防ぎやすい設計になっている

スキーマ管理: マイグレーション

  • 最初のスキーマ投入は、SQLファイルを直接実行する形でもできる
1
2
npx wrangler d1 execute prod-d1-tutorial --local --file=./schema.sql
npx wrangler d1 execute prod-d1-tutorial --remote --file=./schema.sql
  • 継続的にスキーマを変更していく場合は、wrangler d1 migrationsのマイグレーション機能を使う
コマンド内容
wrangler d1 migrations createmigrationsフォルダに、バージョン番号付きの空のSQLファイルを生成する
wrangler d1 migrations list未適用のマイグレーションを一覧表示する
wrangler d1 migrations apply未適用のマイグレーションをデータベースに適用する
  • 適用済みのマイグレーションは、d1_migrationsというテーブル(設定で変更可能)で管理され、開発環境・本番環境をまたいでスキーマの変更履歴をバージョン管理できる
  • マイグレーション適用時は、誤って別のバインディングに対して実行してしまわないよう、バインディング名ではなくデータベース名を指定することが推奨されている

まとめ

  • Cloudflare Workersは、V8 isolateベースの軽量なサーバーレス実行環境で、Lambdaと比べてコールドスタートが速く、デフォルトでエッジに配信される
  • D1は、SQLiteベースのサーバーレスデータベースで、DBを複数作ることで水平にスケールする設計。クエリ数・ストレージ量ベースの課金で、インフラ管理は不要
  • create-cloudflareでWorkerプロジェクトを作り、wrangler d1 createでD1データベースを作成し、wrangler.jsoncにバインディングを追加すれば、Workerのコードからenv.<バインディング名>.prepare().bind().run()という形でDBにアクセスできる
  • スキーマの変更は、初回はSQLファイルの直接実行、継続的な変更はwrangler d1 migrationsのバージョン管理機能を使うのが基本

参考文献

Built with Hugo
テーマ Stack は Jimmy によって設計されています。