データベースを選ぶ

このページ

デプロイごとにデータベースアダプターを 1 つ選びます。データベースはコンテンツモデル、エントリ、ユーザー、設定、プラグインデータを保持します。メディアのバイナリは別のストレージバックエンドに属します。

概要

DatabaseUse it whenRuntime
SQLite1 つの Node.js プロセスが永続ディスクを持つNode.js またはローカル開発
D1サイトが Cloudflare Workers 上で動き、Cloudflare SQL を使うべき場合Cloudflare Workers
Hyperdriveサイトが Workers 上で動き、既存の PostgreSQL オリジンを使う必要がある場合Cloudflare Workers
PostgreSQL複数の Node.js プロセスが 1 つの共有データベースを必要とする場合Node.js
libSQLNode.js デプロイがリモートの SQLite 互換データベースを必要とする場合Node.js

D1 は Cloudflare テンプレートのデフォルトです。SQLite は最も単純な Node.js オプションですが、書き込み可能な永続ボリュームと運用上のデータベースバックアップが必要です。

SQLite

SQLite は Node.js 組み込みのデータベースドライバーを使い、Node.js デプロイ向けの最も単純なオプションです。

import { sqlite } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data.db" }),
		}),
	],
});

設定

OptionTypeDescription
urlstringfile: プレフィックス付きのファイルパス

ファイルパス

url は file: で始まる必要があります:

// Relative path
database: sqlite({ url: "file:./data/emdash.db" });

// Absolute path
database: sqlite({ url: "file:/var/data/emdash.db" });

// From environment variable
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });

Write-ahead logging

EmDash は SQLite データベースを write-ahead logging (WAL) モードで開きます。サイトの実行中、SQLite はデータベースの隣に emdash.db-wal や emdash.db-shm など 2 つの追加ファイルを保持します。プロセスはそれらを作成するためにデータベースディレクトリへの書き込みアクセスが必要です。

-wal ファイルには、まだメインのデータベースファイルに入っていないコミット済みの変更が含まれることがあります。.db ファイルだけをコピーするのではなく、SQLite のバックアップコマンドでバックアップしてください。SQLite のバックアップと復旧を参照してください。

WAL は共有メモリを必要とするため、NFS や SMB などのネットワークファイルシステムではなく、ローカルディスクまたはブロックボリューム上にデータベースを置いてください。

Cloudflare D1

D1 は Cloudflare のサーバーレス SQLite データベースです。Cloudflare Workers にデプロイするときに使います。

import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({ binding: "DB" }),
		}),
	],
});

設定

OptionTypeDefaultDescription
bindingstring—wrangler.jsonc の D1 バインディング名
sessionstring"disabled"リードレプリケーションモード(下記参照)
bookmarkCookiestring"__em_d1_bookmark"セッションブックマークの Cookie 名

Wrangler バインディング

wrangler.jsonc

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "emdash-db"
    }
  ]
}

wrangler.toml

[[d1_databases]]
binding = "DB"
database_name = "emdash-db"

Wrangler はデプロイ中にこのバインディングから不足している D1 データベースをプロビジョニングできます。EmDash のマイグレーションは別ステップです。完全なバインディングセットはCloudflare へのデプロイを、マイグレーションのランブックはコアデータベースマイグレーションの管理に従ってください。

リードレプリカ

D1 は、グローバルに分散したサイトの読み取りレイテンシを下げるためのリードレプリケーションをサポートします。有効にすると、読み取りクエリは常にプライマリに当たるのではなく、近くのレプリカにルーティングされます。

EmDash は D1 Sessions API を使ってこれを透過的に管理します。session オプションで有効にします:

import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({
				binding: "DB",
				session: "auto",
			}),
		}),
	],
});

セッションモード

ModeBehavior
"disabled"セッションなし。すべてのクエリはプライマリへ。デフォルト。
"auto"匿名リクエストは最も近いレプリカから読み取る。認証済みユーザーはブックマーク Cookie で read-your-writes 一貫性を得る。
"primary-first""auto" と同様だが、最初のクエリは常にプライマリへ。書き込みが非常に多いサイト向け。

仕組み

  • 匿名訪問者は first-unconstrained を得ます — 読み取りは最低レイテンシのため最も近いレプリカへ行きます。匿名ユーザーは書き込まないため、一貫性の保証は不要です。
  • 認証済みユーザー(編集者、著者)はブックマークベースのセッションを得ます。書き込み後、ブックマーク Cookie が次のリクエストで少なくともその状態を見ることを保証します。
  • 書き込みリクエスト(POST、PUT、DELETE)は常にプライマリデータベースで始まります。
  • ビルド時クエリ(Astro コンテンツコレクション)はセッションを完全にバイパスし、プライマリを直接使います。

libSQL

libSQL はリモート接続をサポートする SQLite のフォークです。Cloudflare D1 なしでリモートデータベースが必要なときに使います。

import { libsql } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: libsql({
				url: process.env.LIBSQL_DATABASE_URL,
				authToken: process.env.LIBSQL_AUTH_TOKEN,
			}),
		}),
	],
});

設定

OptionTypeDescription
urlstringデータベース URL(libsql://... または file:...)
authTokenstringリモートデータベース用のランタイム認証トークン(ローカルでは任意)
migrationAuthTokenEnvstringマイグレーショントークン変数名(デフォルト TURSO_AUTH_TOKEN)

ローカル開発

開発中はローカルの libSQL ファイルを使います:

database: libsql({ url: "file:./data.db" });

PostgreSQL

PostgreSQL は、完全なリレーショナルデータベースが必要な Node.js デプロイでサポートされています。

import { postgres } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: postgres({
				connectionString: process.env.DATABASE_URL,
			}),
		}),
	],
});

設定

接続文字列または個別パラメータで接続できます:

// Connection string
database: postgres({
	connectionString: "postgres://user:password@localhost:5432/emdash",
});

// Individual parameters
database: postgres({
	host: "localhost",
	port: 5432,
	database: "emdash",
	user: "emdash",
	password: process.env.DB_PASSWORD,
	ssl: true,
});
OptionTypeDescription
connectionStringstringPostgreSQL 接続 URL
hoststringデータベースホスト
portnumberデータベースポート
databasestringデータベース名
userstringデータベースユーザー
passwordstringデータベースパスワード
sslbooleanSSL を有効化
pool.minnumberプールの最小接続数(デフォルト 0)
pool.maxnumberプールの最大接続数(デフォルト 10)
pool.connectionTimeoutMillisnumber最大接続待ち時間(pg デフォルト: 0、タイムアウトなし)
pool.idleTimeoutMillisnumberアイドルクライアントの寿命(pg デフォルト: 10,000 ms)
migrationConnectionStringEnvstringマイグレーション接続文字列変数名(デフォルト DATABASE_URL)

PostgreSQL に到達できない、またはプール接続が利用可能にならない場合にリクエストが待つ時間を制限するには、pool.connectionTimeoutMillis をゼロ以外に設定します。プールが閉じるまでアイドルクライアントを開いたままにするには、pool.idleTimeoutMillis を 0 に設定します。どちらのオプションも省略すると pg のデフォルトが保たれます。

データベースロールの要件

EmDash は自身の PostgreSQL テーブルを作成・更新します。コアマイグレーションはシステムおよびコレクションテーブルを作成・変更し、コンテンツタイプは ec_* テーブルを作成し、フィールドの追加または削除はそのコレクションテーブルを変更します。したがって、設定された PostgreSQL ロールは、初期セットアップ時だけでなくサイトの存続期間にわたってスキーマ権限が必要です。

EmDash 用の正規ロールを使います。次が必要です:

  • データベース上の CONNECT;
  • アクティブなスキーマ上の USAGE と CREATE;
  • すべての EmDash テーブルと関数の所有権(直接、または所有ロールへの INHERIT 付きメンバーシップ経由); および
  • それらのテーブル上の SELECT、INSERT、UPDATE、DELETE。

スーパーユーザーである必要はなく、CREATEDB や CREATEROLE も、拡張の作成も不要です。PostgreSQL はテーブルの ALTER または DROP 権限を提供しません。これらの操作はオブジェクト所有者と、その特権を継承するロールに属します。別のロールにテーブル上の ALL を付与しても、そのロールは所有者になりません。EmDash は SET ROLE を実行しないため、継承なしで設定されたメンバーシップでは不十分です。

ほとんどのインストールは、一般的に public など、データベースの既存スキーマを使えます。データベースが EmDash 専用の場合、これが最も単純なオプションです。以下の例では、emdash_app は EmDash の接続文字列のログインロールです。既存のプロバイダーロールを使うか、専用ログインを作成してください。データベース、スキーマ、ロール名を置き換えて、管理接続でアクセスを付与します:

GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;

これらのグラントはロールに新しいオブジェクトの作成を許可します。既存テーブルの所有者は変更しません。既存サイトに混在する所有者がある場合は、PostgreSQL 所有権修復ランブックを使ってください。

EmDash は PostgreSQL のアクティブな current_schema() を使います。スキーマを作成せず、search_path も設定しないため、デプロイ前に接続を確認してください:

SELECT
  current_database(),
  session_user,
  current_user,
  current_schema(),
  current_setting('search_path');

任意: 専用スキーマを使う

EmDash が別アプリケーションとデータベースを共有する場合、またはオブジェクトを public から分離したい場合は、専用スキーマを使います。任意であり、最初の EmDash セットアップ前に設定するのが最も簡単です。EmDash 専用のデータベースに別スキーマは不要です。

正規の emdash_app ロールが既にあると仮定し、管理接続でそのスキーマを作成して選択します:

GRANT CONNECT ON DATABASE app TO emdash_app;
CREATE SCHEMA emdash AUTHORIZATION emdash_app;
ALTER ROLE emdash_app IN DATABASE app SET search_path = emdash;

これは既存インストールを public から移動せず、混在所有権も修復しません。既存サイトは現在のスキーマを維持し、代わりにPostgreSQL 所有権修復ランブックを使ってください。

コネクションプーリング

アダプターは pg.Pool を使います。デプロイに合わせてプールサイズを調整します:

database: postgres({
	connectionString: process.env.DATABASE_URL,
	pool: { min: 2, max: 20 },
});

Hyperdrive

既存の PostgreSQL — または Postgres 互換(例: PlanetScale Postgres)— データベースをバックエンドに、Cloudflare Workers 上で EmDash を動かすには hyperdrive() アダプターを使います。Hyperdrive は Cloudflare のネットワーク上で接続をプールし加速します。EmDash の PostgreSQL 方言がクエリを実行します。

import { hyperdrive, r2 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: hyperdrive({ binding: "HYPERDRIVE" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

要件

  • サイトに pg >= 8.16.3 がインストールされていること(pnpm add pg)
  • compatibility_flags: ["nodejs_compat"]
  • compatibility_date >= "2024-09-23"

セットアップ

まずPostgreSQL ロールを準備します。次にそのロールの接続文字列で Hyperdrive 設定を作成し、Wrangler 設定にバインディングを追加します:

wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

wrangler.jsonc

{
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "<your-hyperdrive-id>"
    }
  ]
}

wrangler.toml

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<your-hyperdrive-id>"

設定

OptionTypeDefaultDescription
bindingstring"HYPERDRIVE"プライマリ(キャッシュ無効)Hyperdrive バインディング名
cachedBindingstring—匿名読み取り用のオプションのキャッシュ有効バインディング(下記参照)
preferUncachedAfterWriteMsnumber60000*コンテンツ公開後、匿名の公開読み取りでこのミリ秒数の間 binding を優先(Hyperdrive の max_age に合わせる)
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>emdash migrate 用の直接 PostgreSQL オリジン URL を含む環境変数
maxnumber5Hyperdrive への Worker 内接続プールの最大サイズ

*デフォルト 60000 は cachedBinding が設定されている場合のみ適用。それ以外は無視されます。

キャッシュから匿名読み取りを提供する

デフォルトでは Hyperdrive キャッシュを完全に無効にします。管理と書き込みには read-after-write 一貫性が必要だからです。しかし GET または HEAD を使う匿名の公開リクエストは短い古さのウィンドウを許容できます。そのトレードオフが許容できる場合、同じデータベース上で 2 つの Hyperdrive 設定を動かします。キャッシュオフの 1 つ(プライマリ binding)とキャッシュオンの 1 つ(cachedBinding)です。EmDash はそれらの匿名公開リクエストをキャッシュ有効バインディング経由でルーティングし、他のすべてのリクエストはキャッシュなしのプライマリ経由にします。

# Primary — caching OFF (used by admin, auth'd requests, writes, migrations)
wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

# Cached — SAME database role and connection string, caching ON
wrangler hyperdrive create emdash-db-cached \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full"
{
	"hyperdrive": [
		{ "binding": "HYPERDRIVE", "id": "<caching-disabled-id>" },
		{ "binding": "HYPERDRIVE_CACHED", "id": "<caching-enabled-id>" }
	]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });

これはキャッシュについて Cloudflare が文書化している2 設定パターンです。EmDash はリクエストごとにどのバインディングを使うかを決めます:

  • 公開サイトパスの匿名読み取り(GET/HEAD、セッションなし、/_emdash 配下でない)→ キャッシュ有効の cachedBinding。ただしコンテンツ公開後の短いウィンドウ(デフォルト 60 秒。preferUncachedAfterWriteMs を Hyperdrive の max_age に設定)では、EmDash はキャッシュなしの binding を優先し、リビルドがまだ古い Hyperdrive 結果からエッジ/オブジェクトキャッシュを再シードできないようにします。
  • 認証済みリクエスト(編集者、著者)→ キャッシュなしの binding。
  • 変更リクエスト(POST、PUT、PATCH、DELETE、匿名含む)→ キャッシュなしの binding。
  • /_emdash 配下のあらゆるリクエスト(管理、セットアップ、認証、内部 API)、匿名の GET でも → キャッシュなしの binding。
  • ランタイムマイグレーションとコールドスタート → 常にプライマリ binding。
  • デプロイ管理のマイグレーション → migrationConnectionStringEnv を使って PostgreSQL オリジンに直接接続。どちらの Hyperdrive バインディングも使いません。

任意: 別のキャッシュ用ロールを使う

マイグレーション、セットアップ、認証済みリクエスト、明示的な書き込みリクエストは常にプライマリ binding を使います。cachedBinding 用の別ロールにスキーマ所有権や CREATE は不要ですが、CONNECT、スキーマの USAGE、公開サイトが使うすべてのテーブル上の SELECT が必要です。

匿名の公開 GET と HEAD リクエストはリダイレクトヒットと 404 も記録できます。これらの機能を維持するには、キャッシュ用ロールにさらに _emdash_redirects 上の UPDATE と _emdash_404_log 上の SELECT、INSERT、UPDATE、DELETE が必要です。公開の GET または HEAD 中に書き込むプラグインやアプリケーションコードは、さらに必要になることがあります。制限付きキャッシュロールでサイトをテストしていない限り、両方のバインディングに同じロールを使ってください。

EmDash が初期マイグレーションを完了した後にキャッシュ用ロールを追加します。以下の例は任意の emdash スキーマを使います。public などアクティブなスキーマに置き換えてください。プロバイダーの管理ロールでログインとデータベース設定を作成します:

CREATE ROLE emdash_cached LOGIN PASSWORD 'replace-with-a-secret';
GRANT CONNECT ON DATABASE app TO emdash_cached;
ALTER ROLE emdash_cached IN DATABASE app SET search_path = emdash;

次に、スキーマとテーブルの所有者である emdash_app として接続し、既存および将来のテーブルへのアクセスを付与します:

GRANT USAGE ON SCHEMA emdash TO emdash_cached;
GRANT SELECT ON ALL TABLES IN SCHEMA emdash TO emdash_cached;
GRANT UPDATE ON emdash._emdash_redirects TO emdash_cached;
GRANT SELECT, INSERT, UPDATE, DELETE ON emdash._emdash_404_log TO emdash_cached;

ALTER DEFAULT PRIVILEGES IN SCHEMA emdash
  GRANT SELECT ON TABLES TO emdash_cached;

両方のロールで接続し、cachedBinding を有効にする前に同じ current_database() と current_schema() を報告することを確認してください。共有スキーマでは、GRANT SELECT ON ALL TABLES は無関係なテーブルも公開します。代わりに個々の EmDash テーブルへのアクセスを付与し、コレクションや他のスキーマオブジェクトが追加されたときにそれらのグラントを更新してください。

コアマイグレーション

EmDash はサポートされるすべての方言で、デフォルトでコアマイグレーションを自動実行します。Astro の build と sync は、検証済みでシークレットを含まない .emdash/migrations.json も出力し、emdash migrate はデプロイ前にそれを適用できます。SQLite、libSQL、PostgreSQL、D1、および Hyperdrive 背後の直接 PostgreSQL オリジンにはデプロイ実行があります。

ターゲット資格情報、CI 直列化、auto/check/manual ランタイムポリシー、未知のレコードや曖昧な D1 書き込みからの復旧については、コアデータベースマイグレーションの管理を参照してください。

PostgreSQL では、ランタイムマイグレーションは設定された接続経由で実行されます。Hyperdrive のランタイムマイグレーションは常にプライマリバインディングを使います。デプロイ管理の Hyperdrive マイグレーションは PostgreSQL オリジンに直接接続します。コアマイグレーションはテーブル、インデックス、関数の作成、列と制約の変更または削除、既存行の更新を行うことがあります。接続して行を変更できるが既存の EmDash オブジェクトを所有しないロールでは不十分です。ランタイムマイグレーションはセットアップ前に実行されるため、セットアップウィザードは不足しているデータベース特権を修復できません。

データベースが空(コレクションなし)でセットアップウィザードが完了していない場合、EmDash は初回起動時にシードファイルも適用します。シードは .emdash/seed.json、package.json#emdash.seed のパス、または seed/seed.json — 最初に見つかったもの — から読み取られ、コンパイル時にビルドにインラインされます。どれもない場合は組み込みのデフォルトシードが使われます。既存データベースに対するその後の起動はコンテンツをそのままにします。

環境ごとに別のデータベースを使う

開発、プレビュー、ステージング、本番にはそれぞれ独自のデータベースを与えてください。本番を指すプレビューデプロイは、ライブデータに対してコアマイグレーションや破壊的なコンテンツモデルコマンドを実行できます。

Cloudflare では、各 D1 または Hyperdrive バインディングを対応する Wrangler 環境の下に定義し、Wrangler コマンドに --env を渡します。Node.js では、各ランタイム環境に異なるデータベース URL を注入します。資格情報は astro.config.mjs ではなくランタイムシークレットに置いてください。