データベースオプション

このページ

EmDashは複数のデータベースバックエンドをサポートしています。デプロイターゲットに基づいて選択してください。

概要

データベース最適な用途デプロイ
D1Cloudflare Workersエッジ、グローバル分散
HyperdriveCloudflare WorkersでのPostgreSQLエッジ、既存Postgres
PostgreSQL本番 Node.jsPostgresのある任意のプラットフォーム
libSQLリモートデータベースエッジまたはNode.js
SQLiteNode.js、ローカル開発シングルサーバー

Cloudflare D1

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

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

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

設定

オプションデフォルト説明
bindingstringwrangler.jsoncのD1バインディング名
sessionstring"disabled"リードレプリケーションモード(下記参照)
bookmarkCookiestring"__em_d1_bookmark"セッションブックマークのCookie名

セットアップ

wrangler.jsonc

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

wrangler.toml

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

リードレプリカ

D1はグローバルに分散されたサイトの読み取りレイテンシを低減するためにリードレプリケーションをサポートしています。有効にすると、読み取りクエリは常にプライマリデータベースにアクセスする代わりに、近くのレプリカにルーティングされます。

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

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

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

セッションモード

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

仕組み

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

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,
			}),
		}),
	],
});

設定

オプション説明
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,
			}),
		}),
	],
});

設定

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

// 接続文字列
database: postgres({
	connectionString: "postgres://user:password@localhost:5432/emdash",
});

// 個別パラメータ
database: postgres({
	host: "localhost",
	port: 5432,
	database: "emdash",
	user: "emdash",
	password: process.env.DB_PASSWORD,
	ssl: true,
});
オプション説明
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

pool.connectionTimeoutMillisをゼロ以外の値に設定して、PostgreSQLに到達できない場合やプールされた接続が利用できない場合にリクエストが待機する時間を制限します。pool.idleTimeoutMillis0に設定して、プールが閉じるまでアイドルクライアントを開いたままにします。いずれかのオプションを省略するとpgのデフォルトが維持されます。

データベースロール要件

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

EmDash用に1つの標準ロールを使用します。必要なもの:

  • データベースへのCONNECT
  • アクティブスキーマへのUSAGECREATE
  • すべてのEmDashテーブルと関数の所有権(直接、または所有ロールのINHERITメンバーシップを通じて);および
  • それらのテーブルへのSELECTINSERTUPDATEDELETE

スーパーユーザーである必要はなく、CREATEDBCREATEROLEも不要で、エクステンションの作成も不要です。PostgreSQLにはALTERDROPテーブルのgrantがありません:これらの操作はオブジェクト所有者とその権限を継承するロールに属します。テーブルに対するALLを別のロールにgrantしても、そのロールは所有者にはなりません。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;

これらのgrantにより、ロールは新しいオブジェクトを作成できます。既存テーブルの所有者は変更されません。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

hyperdrive()アダプターを使用して、既存のPostgreSQL — またはPostgres互換(例:PlanetScale Postgres)— データベースをバックエンドにCloudflare WorkersでEmDashを実行します。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>"

設定

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

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

キャッシュからの匿名読み取り提供

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

# プライマリ — キャッシュオフ(管理者、認証済みリクエスト、書き込み、マイグレーションで使用)
wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

# キャッシュ付き — 同じデータベースロールと接続文字列、キャッシュオン
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ただしコンテンツ公開後の短いウィンドウ(デフォルト60s;preferUncachedAfterWriteMsをHyperdrive max_ageに設定)では、リビルドがまだ古いHyperdrive結果からエッジ/オブジェクトキャッシュを再シードしないようにEmDashがキャッシュなしbindingを優先。
  • 認証済みリクエスト(エディター、著者)→ キャッシュなしbinding
  • ミューテーションリクエストPOSTPUTPATCHDELETE、匿名を含む)→ キャッシュなしbinding
  • /_emdash下のすべてのリクエスト(管理、セットアップ、認証、内部API)、匿名GETでも → キャッシュなしbinding
  • ランタイムマイグレーションとコールドスタート → 常にプライマリbinding
  • デプロイメント管理マイグレーションmigrationConnectionStringEnvを使用してPostgreSQL originに直接接続;いずれのHyperdriveバインディングも使用しない。

オプション:別のキャッシュロールを使用

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

匿名公開GETおよびHEADリクエストはリダイレクトヒットと404も記録できます。これらの機能を維持するには、キャッシュロールに_emdash_redirectsへのUPDATE_emdash_404_logへのSELECTINSERTUPDATEDELETEが追加で必要です。公開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テーブルにアクセスを付与し、コレクションや他のスキーマオブジェクトが追加された際にそれらのgrantを更新してください。

PostgreSQL混合所有権の修復

サイトが複数のPostgreSQLユーザーを使用した場合、まずプライマリEmDash接続が引き続き使用する標準ロールを選択します。バックアップを取り、所有権の修復中はスキーマ変更を停止します。

アクティブスキーマのすべてのテーブルを検査します:

SELECT
  n.nspname AS schema_name,
  c.relname AS table_name,
  pg_get_userbyid(c.relowner) AS owner
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE n.nspname = current_schema()
  AND c.relkind IN ('r', 'p')
ORDER BY c.relname;

EmDashオブジェクトには_emdash_*および_plugin_*システムテーブル、ec_*コレクションテーブル、content_taxonomiesmediaoptionsrevisionstaxonomiesなどのプレフィックスなしテーブルが含まれます。専用EmDashスキーマでは、すべてのアプリケーションテーブルが標準所有者を持つべきです。

EmDashはメディア使用トリガーで使用されるPostgreSQL関数も作成します。関数の所有権を検査し、修復コマンド用に各関数の引数シグネチャを保持します:

SELECT
  n.nspname AS schema_name,
  p.proname AS function_name,
  pg_get_function_identity_arguments(p.oid) AS arguments,
  pg_get_userbyid(p.proowner) AS owner
FROM pg_proc AS p
JOIN pg_namespace AS n ON n.oid = p.pronamespace
WHERE n.nspname = current_schema()
ORDER BY p.proname, arguments;

所有権を変更できるスーパーユーザーまたはプロバイダーロールで、一致しない各オブジェクトを転送します。常にスキーマ修飾名を使用します:

ALTER TABLE emdash.content_taxonomies OWNER TO emdash_app;
ALTER TABLE emdash.ec_posts OWNER TO emdash_app;
ALTER FUNCTION emdash.emdash_media_usage_capture_work() OWNER TO emdash_app;

ALTER FUNCTION文でインベントリクエリによって返された引数リストを使用します。テーブルの所有者を変更すると、添付されたインデックス、制約、トリガーもカバーされますが、独立したトリガー関数はカバーされません。すべてのEmDashテーブルと関数が標準所有者を報告するまで両方のインベントリクエリを繰り返し、そのロールとして接続してアプリケーションを起動する前にcurrent_schema()を確認します。

非スーパーユーザーが所有権を転送するには、オブジェクトを所有または所有権を継承し、新しい所有者にSET ROLEでき、新しい所有者がスキーマにCREATEを持つ必要があります。マネージドPostgreSQLプロバイダーは転送に管理ロールを必要とする場合があります。

SQLite

SQLiteはNode.jsの組み込みデータベースドライバーを使用し、Node.jsデプロイメントの最もシンプルなオプションです。

import { sqlite } from "emdash/db";

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

設定

オプション説明
urlstringfile:プレフィックス付きファイルパス

ファイルパス

urlfile:で始まる必要があります:

// 相対パス
database: sqlite({ url: "file:./data/emdash.db" });

// 絶対パス
database: sqlite({ url: "file:/var/data/emdash.db" });

// 環境変数から
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });

マイグレーション

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

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

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

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

環境ベースの設定

環境ごとに異なるデータベースを使用します:

import { sqlite, libsql, postgres } from "emdash/db";
import { d1 } from "@emdash-cms/cloudflare";

const database = import.meta.env.PROD ? d1({ binding: "DB" }) : sqlite({ url: "file:./data.db" });

export default defineConfig({
	integrations: [emdash({ database })],
});

選択はビルドモードの代わりに環境変数にも依存できます:

const database = process.env.DATABASE_URL
	? postgres({ connectionString: process.env.DATABASE_URL })
	: sqlite({ url: "file:./data.db" });