コアデータベースマイグレーションの管理

このページ

EmDash のコアマイグレーションは、EmDash 自身のテーブルとコンテンツテーブル上の標準列を更新します。コレクションやフィールドの作成・削除・名前変更は行いません。コンテンツモデルの変更については デプロイ済みサイトの進化 を参照してください。

ランタイムのマイグレーションモードのデフォルトは auto なので、既存のデプロイは起動時に保留中のコアマイグレーションを引き続き適用します。デプロイ管理のマイグレーションでは、新しいアプリケーションコードがトラフィックを受け取る前にビルドがデータベースをマイグレートし、ランタイムがそのデプロイ手順を検証または信頼できます。

コアマイグレーションは前方のみです。確実に完了した文の後にコマンドを再試行できるよう書かれていますが、中断されたリモートコマンドは曖昧な結果を残すことがあります。安全な対応は、全体が走ったとも何も走っていないとも仮定せず、同じデータベースを emdash migrate --status で調べることです。

ビルド、マイグレート、デプロイ、チェック

Astro のビルドまたは同期は .emdash/migrations.json を書き出します。このシークレットなしのマニフェストは、そのビルドが使う正確な EmDash バージョン、順序付きマイグレーションセット、ロケール設定、アダプターのマイグレーション実行器を記録します。

マニフェストを生成した依存関係を持つプロジェクトからこれらのコマンドを実行します。まずビルドしてターゲットを検査します。

pnpm build
pnpm emdash migrate --status

報告されたターゲットが意図したデータベースであることを確認したら、対話型マイグレーションを開始します。確認前にプロンプトでターゲットを再度確認します。同じビルドをデプロイし、デプロイ済みスキーマをチェックします。

pnpm emdash migrate
pnpm wrangler deploy
pnpm emdash migrate --check

emdash migrate --status はデータベースを変更せずに、適用済み・保留中・未知のマイグレーションを報告します。単純な emdash migrate コマンドはターゲットを表示し、保留中のマイグレーションを適用する前に確認を求めます。

--check はマイグレーションを一切適用せず、既知のマイグレーションが保留中か、ビルドに未知のマイグレーション記録がデータベースにある場合に非ゼロで終了します。check の非ゼロ「作業が必要」終了ステータスなしで同じマイグレーションセットを検査したいときは --status を使います。CLI リファレンス は保留、未知、確認、中断、運用の終了コードを区別します。

非対話の適用とすべての --json 適用には --expected-target-fingerprint が必要です。解決されたターゲットが一致しないとコマンドは失敗します。これらのオプションは自動化されたデプロイジョブで使い、上記の対話ワークフローには使いません。

別の場所に保存されたマニフェストには --manifest path/to/migrations.json を使います。ローカル調査では、--from-config [--config astro.config.mjs] が Astro フックを実行したりサーバーを起動したりせずに、信頼できるプロジェクト設定を明示的に評価します。デプロイパイプラインはビルドマニフェストを消費してください。

データベースを明示的に選択する

設定されたアダプターは、シークレットなしのターゲット情報をマニフェストに提供します。資格情報は環境変数に残り、マイグレーションコマンドだけが読み取ります。

AdapterManifest targetDefault credential variableUseful override
SQLiteDatabase path or file: URL—--database <path>
libSQLPublic URLTURSO_AUTH_TOKENConfigure migrationAuthTokenEnv
PostgreSQLConnection variable nameDATABASE_URL--database-url-env <name>
Cloudflare D1Wrangler binding nameCLOUDFLARE_API_TOKEN--d1, --account-id, --wrangler-config, --wrangler-env
HyperdrivePrimary binding and origin variable nameBinding-specific direct-origin variableConfigure migrationConnectionStringEnv

相対 SQLite パスは、インストール済み EmDash パッケージやシェルの現在のサブディレクトリではなく、プロジェクトルートから解決されます。PostgreSQL、libSQL、Hyperdrive のターゲットラベルは資格情報と URL パラメータを省略します。

マイグレート前に D1 をプロビジョニングする

D1 データベースの作成とそのスキーマのマイグレーションは別操作です。emdash migrate が見つからないデータベースを作成することはありません。

  1. データベースをプロビジョニングし、本番 UUID を記録します。

    pnpm wrangler d1 create my-site-production
  2. その UUID を wrangler.jsonc の意図したバインディングと環境に追加します。

  3. D1 バインディングが .emdash/migrations.json に記録されるようサイトをビルドします。

  4. アカウント ID と D1 Edit 権限を持つスコープ付き API トークンを設定します。選択したターゲットを検査してから対話型マイグレーションを実行します。アカウントとデータベースが意図した本番データベースと一致するときだけプロンプトを確認します。

    export CLOUDFLARE_ACCOUNT_ID="..."
    export CLOUDFLARE_API_TOKEN="..."
    pnpm emdash migrate \
      --status \
      --wrangler-config wrangler.jsonc \
      --wrangler-env production
    pnpm emdash migrate \
      --wrangler-config wrangler.jsonc \
      --wrangler-env production

代わりに --account-id と --d1 <database-uuid-or-name> を指定できます。名前の解決はちょうど 1 つのデータベースに解決される必要があります。プレビュー ID、プレースホルダー ID、競合するアカウント、曖昧なバインディングは fail closed します。

CI で D1 マイグレーションを設定する

EmDash はマイグレーション適用中、D1 データベースにマイグレーションロックを保持します。これは emdash migrate と auto モードのランタイムマイグレーションの両方です。ロック保持中に始まる 2 回目の実行は最大 10 秒待ちます。その時間内に最初の実行が終われば、2 回目は何も適用せず成功します。そうでなければマイグレーションを適用せず失敗します。アカウントとデータベース UUID ごとに一度に 1 つのマイグレーションジョブを実行し、2 つ目のジョブは失敗する代わりに CI キューで待つようにします。

CI 環境に次のシークレットと変数を設定します。

  • シークレット CLOUDFLARE_API_TOKEN: D1 Edit 権限を持つスコープ付きトークン。
  • 変数 CLOUDFLARE_ACCOUNT_ID: データベースを所有する Cloudflare アカウント ID。
  • 変数 D1_DATABASE_ID: 本番 D1 データベース UUID。
  • 変数 EMDASH_TARGET_FINGERPRINT: アカウントとデータベースをローカルで確認した後に emdash migrate --status が出力するフィンガープリント。

次の GitHub Actions ワークフローはこれらの値を使い、不変の D1 識別子の両方で concurrency グループをキーにします。適用ステップは非対話なので、確認済みのターゲットフィンガープリントを明示的に渡します。

name: Deploy

on:
  workflow_dispatch:

concurrency:
  group: emdash-migrations-${{ vars.CLOUDFLARE_ACCOUNT_ID }}-${{ vars.D1_DATABASE_ID }}
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - name: Inspect EmDash migration target
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        run: |
          pnpm emdash migrate --status --json \
            --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
            --d1 "${{ vars.D1_DATABASE_ID }}"
      - name: Apply EmDash migrations
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          EMDASH_TARGET_FINGERPRINT: ${{ vars.EMDASH_TARGET_FINGERPRINT }}
        run: |
          pnpm emdash migrate \
            --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
            --d1 "${{ vars.D1_DATABASE_ID }}" \
            --expected-target-fingerprint "$EMDASH_TARGET_FINGERPRINT"
      - run: pnpm wrangler deploy
      - name: Check EmDash migrations
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        run: |
          pnpm emdash migrate --check \
            --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
            --d1 "${{ vars.D1_DATABASE_ID }}"

変更されたターゲットをローカルで確認した後にのみ EMDASH_TARGET_FINGERPRINT を更新します。フィンガープリントに資格情報は含まれませんが、アカウントとデータベースを確認せずに変更すると、誤ったデータベースをマイグレートする防御がなくなります。

固着したマイグレーションロックを解放する

マイグレーションロックを解放する前に停止した D1 マイグレーション実行は、ロックを保持したままにします。これは CI ジョブが emdash migrate 中にキャンセルされたとき、auto モードの Worker がランタイムマイグレーション中に停止したとき、または開発サーバーがマイグレーション適用中に停止されたときに起きます。マイグレーションエラーで失敗した実行はロックを解放します。EmDash は保持していないロックを解放しません。ホルダーがまだマイグレーションを適用中か、途中で停止した可能性があるためです。ロックが解放されるまで、サイトは保留中のマイグレーションを適用できず、auto モードでは EmDash の初期化に失敗します。

ロックが 1 分より長く保持されると、emdash migrate とランタイムマイグレーションは待機をやめ、次のエラーを報告します。

The migration lock has been held since 2026-09-01T12:00:00.000Z (lock 1788264000000). A migration may still be running; if none is, check the database and release the lock: https://docs.emdashcms.com/deployment/core-migrations/#release-a-stuck-migration-lock

リモート D1 データベースのロック解放には emdash migrate を使うため、.emdash/migrations.json を書いたビルドと D1 Edit 権限付き API トークンが必要です。マイグレート前に D1 をプロビジョニングする を参照してください。

  1. マイグレーションジョブ、デプロイ、または他の emdash migrate コマンドがそのデータベースに対して実行中でないことを確認します。

  2. マイグレーションが使ったのと同じターゲットオプションでロックとマイグレーションセットを検査します。

    pnpm emdash migrate --status

    レポートはロックとその id から始まります。停止した実行がマイグレーションを適用中だった場合、それは最初の保留マイグレーションであり、部分的に適用されている可能性があります。

    Migration lock: held since 2026-09-01T12:00:00.000Z (id 1788264000000)

    auto モードの Worker もマイグレーション適用中にロックを保持できます。1 分以上後にもう一度コマンドを実行し、既知の適用済みマイグレーションが変わり続ける間はロックを解放せずに待ちます。

  3. その id でロックを解放します。コマンドはターゲットの確認を求め、ロックがまだその id を持つ間だけ解放します。非対話シェルでは、--status が出力したターゲットフィンガープリント付きの --expected-target-fingerprint を追加します。

    pnpm emdash migrate --release-lock 1788264000000
  4. 保留中のマイグレーションを再度適用します。

    pnpm emdash migrate

    最初の保留マイグレーションで適用が失敗した場合、そのマイグレーションを途中で止まったものとして扱い、トラブルシューティング の曖昧な D1 書き込みの項目に従ってください。

emdash migrate が到達するのはリモート D1 データベースだけです。開発サーバーのローカル D1 データベースでロックが保持されている場合は、サーバーを停止し、Wrangler でロックをクリアします。DB をバインディング名に、数字をエラーのロック id に置き換えてください。

pnpm wrangler d1 execute DB --local --command "UPDATE _emdash_migrations_lock SET is_locked = 0 WHERE is_locked = 1788264000000"

Hyperdrive はオリジンに接続する

Hyperdrive のマイグレーション実行器は、オリジンへの直接 PostgreSQL 接続を開きます。マイグレーショントラフィックを Hyperdrive 経由で送らず、オプションのキャッシュバインディングも使わず、Worker からプライベートネットワーク到達性を継承しません。

デプロイランナーはオリジンに到達できなければなりません。デフォルトのバインディング固有変数が不適切なときは hyperdrive() に migrationConnectionStringEnv を設定し、その変数はマイグレーションジョブだけに渡します。ランタイムの Hyperdrive 資格情報と直接オリジンのデプロイ資格情報は分離してください。

ランタイム強制を段階的に導入する

次の EmDash 統合設定は、開発では自動マイグレーションを維持しつつランタイム強制を有効にします。

emdash({
	database,
	migrations: {
		runtime: "check",
		dev: "auto",
	},
});
  • auto は後方互換のデフォルトです。ランタイム起動時に保留中のマイグレーションを確認して適用します。
  • check は一方向のステータスクエリを行い、既知のマイグレーションが保留中ならリクエストを処理する前に 503 を返します。ローリングデプロイ中は、より新しい互換ビルドの記録を許容します。
  • manual はランタイムマイグレーションもステータスクエリも行いません。デプロイパイプラインがすべてのビルドを確実に適用・チェックした後にだけ使います。

同じ成果物が複数環境に昇格されるとき、EMDASH_MIGRATIONS_MODE でランタイムモードを上書きできます。セットアップと開発のバイパスルートは実効モードに従い、check や manual の背後で静かにマイグレートできません。

保守的なロールアウトは、デプロイジョブ導入中は auto、ジョブが信頼できるようになったら check、すべてのデプロイに外部チェックが強制されたら manual です。

ローリングデプロイ中の互換性

コアマイグレーションは expand/deploy/contract の順序に従います。デプロイは一時的に古いアプリケーション isolate と新しい isolate を拡張済みデータベースに対して実行でき、バックフィルがまだ進行中のこともあります。デプロイされたすべてのバージョンがそのスキーマの使用を止めるまで、スキーマを contract しないでください。

未知の適用済みマイグレーション記録は、このローリングデプロイ方向に限りランタイム check が許容します。CLI の厳密チェックはそれらを報告し、apply は変更を拒否します。データベースがより新しいか、分岐したマイグレーション履歴を持つ可能性があるためです。

ロールバック境界

以前のアプリケーション成果物をデプロイしてもコアマイグレーションは逆になりません。保留中のマイグレーションを適用する前に、復元可能なデータベースバックアップを取り、それに一致するアプリケーション成果物を記録します。以前のアプリケーションがマイグレート後のスキーマで実行できない場合は、マイグレーション前のデータベースとアプリケーションを一緒に復元します。運用ロールバックとして _emdash_migrations から行を削除したり、マイグレーションの内部 down() を実行したりしないでください。

混在した PostgreSQL 所有権を修復する

既存の PostgreSQL サイトが複数の所有者で EmDash オブジェクトを作成し、後のマイグレーションが must be owner of table などのエラーで失敗する場合にこのランブックを使います。プライマリ 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_taxonomies、media、options、revisions、taxonomies のようなプレフィックスなしテーブルが含まれます。専用の 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;

テーブルの所有者変更はその添付インデックス、制約、トリガーもカバーしますが、独立したトリガー関数はカバーしません。すべての EmDash テーブルと関数が正規所有者を報告するまで両方のインベントリクエリを繰り返します。そのロールで接続し、トラフィックを再開する前に current_database()、current_schema()、マイグレーションステータスを確認します。

非スーパーユーザーが所有権を移譲するには、オブジェクトを所有するか所有権を継承し、新しい所有者へ SET ROLE でき、新しい所有者がスキーマに CREATE を持っている必要があります。マネージド PostgreSQL プロバイダーは移譲に管理ロールを要求することがあります。

トラブルシューティング

  • No migration manifest found. Build or sync the project first. Use --manifest for a non-standard artifact location or explicitly choose --from-config for local investigation.
  • The artifact does not match project EmDash. Rebuild and deploy the application and manifest together. Run the project’s CLI instead of a global installation.
  • The target is missing or ambiguous. Provision it first, then supply an explicit database path, connection-variable name, D1 selector, or selected Wrangler config and environment. EmDash does not guess from unrelated environment variables or bindings.
  • The target fingerprint changed. Stop and review the displayed account, environment, database name, UUID, or path. Update the expected fingerprint only after confirming the intended target.
  • Unknown migration records are present. Do not delete the records or rerun apply. Confirm that the application artifact is the intended version and investigate whether a newer or divergent build migrated the database.
  • A D1 write outcome is ambiguous. Do not replay the migration command. Run emdash migrate --status against the same account and database UUID, inspect the result, and escalate if the migration stopped part-way through.
  • Datetime normalization requires manual review. A legacy datetime falls in a repeated or skipped daylight-saving hour in the site’s configured timezone. The error lists each affected content row or revision. Correct those values with an explicit UTC offset, then retry the migration. The migration preflight does not write any datetime until every stored value can be resolved.
  • Hyperdrive cannot connect. Test reachability from the deployment runner to the PostgreSQL origin and verify the direct-origin variable. Worker-to-Hyperdrive connectivity does not prove the runner can reach the origin.