復旧ポイントは、データベース全体を復元できなければなりません。EmDash の JSON バックアップは確認やカスタムツールには便利ですが、EmDash では復元できません。データベースのマイグレーションや破壊的な操作の前には、生のデータベースバックアップまたはポイントインタイムリカバリを使用してください。
メディアのバイナリは別途バックアップしてください。サイトが暗号化されたプラグイン設定を保存している場合は、EMDASH_ENCRYPTION_KEY のローテーションリスト全体を、シークレットマネージャーの別のバックアップに保管してください。データベースのバックアップやエクスポートには、これらのファイルやキーは含まれません。
復旧方法を選ぶ
サイトで設定されているデータベースアダプターに合った方法を使用します。
| データベース | 復旧ポイント |
|---|---|
| Cloudflare D1 | Time Travel のブックマークを記録します。必要な保持期間が Time Travel を超える場合や、復元のリハーサルが必要な場合は、オフサイトの SQL ダンプを作成します。 |
| SQLite またはローカルの libSQL | SQLite のオンラインバックアップコマンドを使い、コピーに対して整合性チェックを実行します。 |
| PostgreSQL | プロバイダーのポイントインタイムリカバリを使うか、pg_dump でカスタム形式のアーカイブを作成します。 |
| Hyperdrive | オリジンへの直接接続で、その PostgreSQL オリジンをバックアップします。Hyperdrive のバインディングはバックアップ用の接続ではありません。 |
| Turso ホスト型の libSQL | Turso のポイントインタイムリカバリを使うか、SQL ダンプを作成してリハーサルします。 |
| その他のリモート libSQL プロバイダー | プロバイダーのデータベース全体のエクスポートまたはポイントインタイムリカバリの手順を使い、分離したデータベースに対してテストします。 |
復旧ポイントは、対応するアプリケーションアーティファクト、メディアのバックアップ、キーリストのバージョンと一緒に保管してください。計画しているマイグレーションの前に、正確なデータベースと環境を記録し、プロバイダーの手順で必要な場合はアプリケーションの書き込みを止め、想定しているロールバック期間中、復旧ポイントが引き続き利用できることを確認します。
メディアオブジェクトのバックアップと復元
R2 や S3 互換のバケットには、データベースに加えてオブジェクト単位のバックアップが必要です。次の AWS CLI コマンドは、backups/ 配下の EmDash の JSON アーカイブを含むすべてのオブジェクトを、ローカルのバックアップディレクトリにコピーします。AWS S3 の場合は --endpoint-url を省略します。
aws s3 sync s3://emdash-media ./emdash-media-backup \
--endpoint-url https://<account-id>.r2.cloudflarestorage.com
<account-id> を Cloudflare のアカウント ID に置き換えてください。定例のバックアップジョブには、読み取り専用のバケット資格情報を使います。バックアップは本番アカウントや障害ドメインの外に保存し、同時に作成したデータベースの復旧ポイントを記録します。
本番環境がリクエストを処理している間に上書きするのではなく、空の復旧用バケットに復元します。
aws s3 sync ./emdash-media-backup s3://emdash-media-recovery \
--endpoint-url https://<account-id>.r2.cloudflarestorage.com
復元ジョブには、復旧用バケットだけへの書き込みアクセスを与えます。非本番のデプロイを、復元したデータベースとバケットに向け、既知のメディア URL をいくつか開き、使い捨てのファイルをアップロードして削除します。データベースとメディアの両方がチェックに合格してから、本番のバインディングやバケットの設定を変更します。
ローカルストレージの場合は、local() ストレージアダプターで設定したディレクトリをコピーします。コピー中はメディアの書き込みを止め、コピーした中から代表的なファイルを確認します。
Time Travel で D1 データベースを復旧する
マイグレーションの前に、Time Travel でデータベースの現在のブックマークを取得し、出力全体をデプロイと一緒に記録します。
npx wrangler d1 time-travel info my-database
Time Travel の復旧ポイントには期限があります。Cloudflare では現在、Workers Free プランで 7 日間、Workers Paid プランで 30 日間保持されます。ロールバック期間をそれより長くする必要がある場合は、オフサイト D1 ダンプを作成してください。
復旧が必要になったら、サイトへの書き込みを止め、マイグレーションの前に記録したブックマークそのものを復元します。
npx wrangler d1 time-travel restore my-database \
--bookmark="<pre-migration-bookmark>"
<pre-migration-bookmark> を、記録したブックマークに置き換えてください。このコマンドはデータベースをその場で上書きし、実行中のクエリをキャンセルします。復元コマンドが出力する復元前のブックマークを保存しておくと、復元そのものを元に戻せます。
Time Travel は、コンテンツ、ユーザー、設定、プラグインデータ、マイグレーションの記録を復元します。R2 のメディアオブジェクトや EMDASH_ENCRYPTION_KEY は復元しません。トラフィックを再開する前に、対応するアプリケーションアーティファクト、メディア、キーを復元してください。サインイン、コンテンツの読み取り、スキーマ、暗号化されたプラグイン設定、書き込みを確認します。
現在の保持期間、要件、復元時の動作については、D1 Time Travel のドキュメントを参照してください。
オフサイト D1 ダンプを作成する
(ユーザーや認証のテーブルを含む)生のデータベースの SQL ダンプには、Wrangler を使います。EmDash は検索インデックスを FTS5 の仮想テーブルに保持しているため、いずれかのコレクションで検索を有効にすると、単純な wrangler d1 export my-database --remote は D1 Export error: cannot export databases with Virtual Tables (fts5) で失敗します。それ以外のテーブルを名前で指定してエクスポートし、検索インデックスは除外してください。復元したサイトが検索インデックスを再構築します。
復元が成功するように、ダンプは 4 つのファイルに分割します。
backup-schema.sqlはテーブルを作成します。backup-folders.sqlはメディアフォルダーを保持します。大きなインポートは分割して実行されるため、そうしないとメディア項目が所属するフォルダーより先に挿入され、外部キーのチェックで失敗します。backup-data.sqlはその他のすべての行を保持します。backup-indexes.sqlは、テーブル名を指定したエクスポートでは除外されるインデックスとトリガーを保持します。行のインポート中にトリガーが発火しないよう、これらは最後に作成します。
これらのコマンドには jq が必要です。次のコマンドで 4 つのファイルを書き出します。
npx wrangler d1 execute my-database --remote --json \
--command "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT GLOB '_emdash_fts_*' AND name NOT GLOB 'sqlite_*' AND name NOT GLOB '_cf_*' AND name != 'media_folders'" \
| jq -r '.[0].results[] | "--table=" + .name' > backup-tables.txt
npx wrangler d1 export my-database --remote --no-data --output=backup-schema.sql \
--table=media_folders $(cat backup-tables.txt)
npx wrangler d1 export my-database --remote --no-schema --output=backup-folders.sql \
--table=media_folders
npx wrangler d1 export my-database --remote --no-schema --output=backup-data.sql \
$(cat backup-tables.txt)
npx wrangler d1 execute my-database --remote --json \
--command "SELECT sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND sql IS NOT NULL AND name NOT GLOB '_emdash_fts_*' AND tbl_name NOT GLOB '_emdash_fts_*' AND tbl_name NOT GLOB '_cf_*'" \
| jq -r '.[0].results[] | .sql + ";"' > backup-indexes.sql
Wrangler のエクスポートオプションと制限については、D1 のインポートとエクスポートのドキュメントを参照してください。
SQL ファイルは、対応するアプリケーションのバージョン、同時に作成したメディアのバックアップ、別途保管した EMDASH_ENCRYPTION_KEY のローテーションリストのコピーと一緒に保管してください。復旧のテストでは、新しく用意した空の D1 データベースにファイルをインポートし、ランタイムのキーを復元し、非本番のバインディングをそのデータベースに更新して、サイトを確認します。
空の復旧用データベースへは、次の順序でファイルをインポートします。
npx wrangler d1 execute my-recovery-database --remote --file=backup-schema.sql
npx wrangler d1 execute my-recovery-database --remote --file=backup-folders.sql
npx wrangler d1 execute my-recovery-database --remote --file=backup-data.sql
npx wrangler d1 execute my-recovery-database --remote --file=backup-indexes.sql
復元したデータベースには検索インデックスがありません。EmDash は、復元したサイトで検索 API(/_emdash/api/search)が初めて呼び出されたときに、インデックスを再構築します。たとえば、管理画面のコマンドパレットや、API を使う検索ボックスから呼び出されます。それまでは、search() を直接呼び出すページは結果を返しません。すぐに再構築するには、サイトの起動後に /_emdash/api/search?q=a を一度リクエストします。
SQLite とローカル libSQL をバックアップする
オフラインでバックアップする場合は、データベースに書き込むすべてのプロセスを止め、データベースファイルと、その隣にある -wal および -shm ファイルをあわせてコピーします。-wal ファイルには、まだメインファイルに反映されていないコミット済みの変更が含まれていることがあります。
整合性のあるオンラインバックアップには、データベースアダプターで設定したパスに対して SQLite のバックアップコマンドを使います。
sqlite3 emdash.db ".backup backup.db"
コピーしたデータベースを確認します。コマンドは ok を出力する必要があります。
sqlite3 backup.db "PRAGMA integrity_check"
非本番環境で、対応するアプリケーションのバージョンでコピーを開き、サインイン、公開コンテンツ、暗号化されたプラグイン設定、編集、メディアの読み取りを確認します。
復旧するには、データベースに書き込むすべてのプロセスを止め、破損したデータベースとその -wal および -shm ファイルのコピーを保管したうえで、検証済みのバックアップに置き換え、トラフィックを再開する前に、対応するアプリケーションアーティファクト、メディア、キーを復元します。
PostgreSQL と Hyperdrive をバックアップする
PostgreSQL プロバイダーが、本番を変更する前にテストできる分離された復元を提供している場合は、そのポイントインタイムリカバリやスナップショット機能を使います。正確なタイムスタンプまたはスナップショット ID と、保持期間を記録します。
移植可能なバックアップには、PostgreSQL オリジンに直接接続して、カスタム形式のアーカイブを作成します。
pg_dump --format=custom --file=emdash.dump "$DATABASE_URL"
pg_dump は、データベースを使用中のままでも、トランザクション的に整合性のあるアーカイブを作成します。アーカイブに頼る前に、その目次を確認します。
pg_restore --list emdash.dump > emdash.dump.list
空の復旧用データベースを用意します。正規の EmDash アプリケーションロールで接続し、元のオーナー情報を復旧用データベースに持ち込まずに復元します。
pg_restore --exit-on-error --no-owner \
--dbname="$RECOVERY_DATABASE_URL" emdash.dump
RECOVERY_DATABASE_URL のロールが、復元された EmDash オブジェクトのオーナーになります。復旧後のアプリケーションが使用するのと同じ、安定したロールを使ってください。非本番のデプロイを復旧用データベースに向け、サインイン、コンテンツの読み取り、スキーマ、暗号化されたプラグイン設定、書き込みを確認します。
Hyperdrive の場合、DATABASE_URL は、migrationConnectionStringEnv で指定された PostgreSQL オリジンへの直接接続である必要があります。Worker のバインディングは、PostgreSQL のバックアップツールに渡せません。オリジンのプロバイダーの復旧手順に従い、Hyperdrive の設定をアプリケーションアーティファクトと一緒に保管してください。
プロバイダー管理の復旧、大規模なデータベース、継続的アーカイブについては、PostgreSQL のバックアップのドキュメントを参照してください。
Turso ホスト型 libSQL をバックアップする
Turso のポイントインタイムリカバリは、以前のタイムスタンプから別のデータベースを作成します。マイグレーションの直前に、RFC 3339 形式のタイムスタンプを記録します。復旧が必要になったら、その時点から分離したデータベースを作成します。
turso db create emdash-recovery \
--from-db emdash-production \
--timestamp <pre-migration-rfc3339-timestamp>
<pre-migration-rfc3339-timestamp> を、記録したタイムスタンプに置き換えてください。復旧したデータベース用のトークンを作成し、非本番のデプロイをそのデータベースに向け、本番の接続を変更する前にサイトを確認します。サイトのプランで提供される保持期間は、Turso のポイントインタイムリカバリで確認してください。
オフサイトのコピーには、データベース全体をダンプします。
turso db shell emdash-production .dump > emdash.sql
ダンプから分離したデータベースを作成して、ダンプをテストします。
turso db create emdash-recovery --from-dump ./emdash.sql
その他のリモート libSQL プロバイダーの場合は、そのプロバイダーのデータベース全体のエクスポートまたはポイントインタイムリカバリの手順に従ってください。その手順で復旧ポイントが作成され、分離した復元のテストが済むまでは、リモートのマイグレーションを適用しないでください。
JSON バックアップを確認用に使う
JSON バックアップには次のものが含まれます。
- 下書き、予約投稿、ゴミ箱の項目を含む、すべてのコンテンツエントリ
- コンテンツモデルを構成するコレクションとフィールドの定義
- タクソノミーの定義、ターム、各エントリに割り当てられたターム
- メニューとメニュー項目、セクション、ウィジェットエリアとウィジェット、SEO レコード、リビジョン履歴、メディアのメタデータ、データベースのマイグレーション履歴
site:設定グループと、emdash:site_title、emdash:site_tagline、emdash:localeの各設定にある、タイトル、キャッチフレーズ、URL、ロケール、ロゴ、表示設定、ソーシャルプロフィール、SEO のデフォルトなどのサイト設定
次のものは含まれません。
- ユーザーアカウント、セッション、パスキー、OAuth データ、API トークン、その他の認証データ
- プラグインのストレージと設定(プラグインのシークレットを含む)
- コメントとリアクション、リダイレクトと 404 ログ、バイライン、コンテンツのリレーションと参照、監査ログ、レート制限、スケジュールされたタスクの状態
- メディアフォルダー、メディアの使用箇所の記録、未完了のアップロード、メディアファイルそのもの
Settings → Backups の Download backup で、新しい JSON ファイルを生成します。ストレージが設定されている場合、Back up now は backups/ プレフィックスの下に JSON ファイルを 1 つ保存します。これらのファイルは、EmDash のプレビューと同じ、バージョン付きのスナップショット形式を使います。
JSON バックアップをスケジュールするには、Settings → Backups で Daily automatic backups を有効にし、1〜30 個のファイルから保持数を選びます。バックアップはスケジュールされたメンテナンスの間に実行されます。Cloudflare では Worker に cron トリガーが必要で、Node では組み込みのスケジューラーがタスクを実行します。
バケットにパブリックな R2 ドメインやコンテンツ配信ネットワークのオリジンがある場合は、そのオリジンをメディアオブジェクトだけに制限するか、EmDash 経由でメディアを配信してください。EmDash のメディアルートは backups/ の配信を拒否しますが、パブリックなバケットのオリジンでは、URL からすべてのオブジェクトが公開されるおそれがあります。
コンテンツ、設定、メディアを新しい EmDash サイトにコピーするには、サイトパッケージを使います。サイトパッケージはユーザー、API トークン、プラグインデータ、シークレットを復元しないため、データベースの復旧ポイントの代わりにはなりません。