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

このページ

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 のフックを実行したりサーバーを起動したりせずに、信頼できるプロジェクト設定を明示的に評価します。デプロイパイプラインでは、ビルドのマニフェストを使用してください。

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

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

アダプターマニフェストのターゲットデフォルトの認証情報変数便利なオーバーライド
SQLiteデータベースパスまたは file: URL—--database <path>
libSQL公開 URLTURSO_AUTH_TOKENmigrationAuthTokenEnv を設定
PostgreSQL接続変数名DATABASE_URL--database-url-env <name>
Cloudflare D1Wrangler バインディング名CLOUDFLARE_API_TOKEN--d1、--account-id、--wrangler-config、--wrangler-env
Hyperdriveプライマリバインディングとオリジン変数名バインディング固有の直接オリジン変数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、競合するアカウント、曖昧なバインディングは、フェイルクローズド(安全側に倒して失敗)になります。

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

EmDash は、マイグレーションを適用している間、D1 データベース内でマイグレーションロックを保持します。これは emdash migrate でも、auto モードのランタイムマイグレーションでも同じです。ロックが保持されている間に開始された 2 回目の実行は、最大 10 秒待機します。その間に 1 回目の実行が完了すれば、2 回目の実行は何も適用せずに成功します。そうでなければ、マイグレーションを適用せずに失敗します。2 つ目のジョブが失敗せずに CI のキューで待機するよう、アカウントと D1 データベース UUID ごとに、一度に 1 つのマイグレーションジョブだけを実行してください。

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

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

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

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 のマイグレーション実行が、マイグレーションロックを解放する前に停止すると、ロックが保持されたままになります。これは、emdash migrate の実行中に CI ジョブがキャンセルされた場合、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 を使うため、マイグレート前に D1 をプロビジョニングするで説明したように、.emdash/migrations.json を書き出したビルドと、D1 Edit 権限を持つ API トークンが必要です。

  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 は一方向のステータスクエリを 1 回実行し、既知のマイグレーションが保留中の場合は、リクエストを処理する前に 503 を返します。ローリングデプロイ中は、より新しい互換ビルドの記録を許容します。EmDash がインストールする画像エンドポイントへのリクエストは、このチェックを実行するランタイムの起動処理をスキップするため、引き続き処理されます。
  • manual は、ランタイムでのマイグレーションもステータスクエリも行いません。デプロイパイプラインがすべてのビルドを確実に適用してチェックするようになってから、使用してください。

同じ成果物を複数の環境へ昇格させる場合は、EMDASH_MIGRATIONS_MODE でランタイムモードをオーバーライドできます。セットアップと開発のバイパスルートは、実際に有効なモードに従います。check や manual の裏で、黙ってマイグレートすることはできません。

慎重な導入の進め方は、デプロイジョブを導入する間は auto、ジョブが安定したら check、すべてのデプロイで外部チェックを強制できるようになったら manual です。

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

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

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

ロールバック境界

以前のアプリケーション成果物をデプロイしても、コアマイグレーションは元に戻りません。保留中のマイグレーションを適用する前に、復元可能なデータベースバックアップを取得し、それに対応するアプリケーション成果物を記録してください。以前のアプリケーションが、マイグレート後のスキーマに対して動作しない場合は、マイグレート前のデータベースとアプリケーションを一緒に復元します。運用上のロールバックとして、_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 テーブルと関数が正規の所有者を報告するまで、2 つの確認クエリを繰り返してください。その後、そのロールで接続し、トラフィックを再開する前に、current_database()、current_schema()、マイグレーションのステータスを確認します。

スーパーユーザー以外が所有権を移管するには、そのオブジェクトを所有しているか所有権を継承していること、新しい所有者に SET ROLE できること、そして新しい所有者がそのスキーマに対する CREATE 権限を持っていることが必要です。マネージドな PostgreSQL プロバイダーでは、移管の実行にプロバイダーの管理者ロールが必要な場合があります。

トラブルシューティング

  • マイグレーションマニフェストが見つからない。 先にプロジェクトをビルドまたは同期してください。標準以外の成果物の場所には --manifest を使うか、ローカルでの調査であれば --from-config を明示的に選択します。
  • 成果物がプロジェクトの EmDash と一致しない。 アプリケーションとマニフェストを一緒にリビルドしてデプロイしてください。グローバルにインストールした CLI ではなく、プロジェクトの CLI を実行します。
  • ターゲットが存在しない、または曖昧。 先にプロビジョニングしてから、データベースパス、接続変数名、D1 セレクター、または選択した Wrangler 設定と環境を明示的に指定してください。EmDash は、無関係な環境変数やバインディングから推測することはありません。
  • ターゲットのフィンガープリントが変わった。 作業を止めて、表示されたアカウント、環境、データベース名、UUID、パスを確認してください。意図したターゲットであることを確認してから、期待するフィンガープリントを更新します。
  • 不明なマイグレーション記録がある。 記録を削除したり、適用を再実行したりしないでください。アプリケーション成果物が意図したバージョンであることを確認し、より新しいビルド、または分岐したビルドがデータベースをマイグレートしていないかを調査してください。
  • D1 の書き込み結果が曖昧。 マイグレーションコマンドを再実行しないでください。同じアカウントと D1 データベース UUID に対して emdash migrate --status を実行して結果を確認し、マイグレーションが途中で止まっている場合はエスカレーションしてください。
  • 日時の正規化に手動確認が必要。 古い形式の日時が、サイトに設定されたタイムゾーンで繰り返される、またはスキップされる夏時間の時間帯に該当しています。エラーには、影響を受ける各コンテンツ行またはリビジョンが一覧表示されます。それらの値を明示的な UTC オフセット付きで修正してから、マイグレーションを再試行してください。マイグレーションのプリフライトでは、保存されているすべての値が解決できるようになるまで、日時は一切書き込まれません。
  • Hyperdrive が接続できない。 デプロイ実行環境から PostgreSQL のオリジンへの到達性をテストし、直接オリジン変数を確認してください。Worker から Hyperdrive への接続性があっても、実行環境がオリジンに到達できることの証明にはなりません。