ブログ一覧

戻せない D1 マイグレーションは本番のダンプで先に流す

サイト運営で使っている EmDash をバージョンアップする PR で20本のマイグレーションが対象になりました。EmDash はデプロイ後の最初のリクエストで未適用のマイグレーションを自動で当てる構成なので、この PR は merge した瞬間に本番のスキーマを書き換えました。

20本のうち2本は、適用したスキーマ変更を元に戻す down マイグレーションが空で戻せないため、戻せないマイグレーションは本番のダンプに先に流してから merge する、という設計にしました。

merge がそのままスキーマ変更の引き金になる構成

EmDash はマイグレーションの適用も自分で引き受けます。デプロイが終わった時点ではなく、その後最初にリクエストを受けた瞬間に未適用のマイグレーションを当てます。

コードだけを git revert で戻しても、スキーマは新しいバージョンのまま残ります。EmDash は管理テーブル _emdash_migrations の行数を数え、ビルドが把握している本数以上あれば実行せずに抜ける「fast-path」を持っていて、行数しか見ていないからです。結果として、古いバージョンのコードが新しいスキーマに向けて問い合わせを続ける状態になります。

8 サイトは 1 つの monorepo で管理していますが、デプロイの経路に段階的な仕組みを用意していないため、対象パッケージを含む PR は merge すると 8 サイトが一括でデプロイされ、1 サイトで試してから残りに広げる余地がありません。EmDash を 0.29.0 から 0.34.0 へ上げる PR が、まさにこの形でした。

本番のダンプで先にリハーサルする

そこで、毎晩のバックアップが残す本番のダンプに先にマイグレーションを当てました。このダンプを空の SQLite に流し込めば、本番と同じスキーマとなりリハーサルとしてちょうど良かったためです。

8サイトのうち2サイトのダンプに当て、結果は以下の通りです。

  • 20本すべて成功し、ローカルの SQLite で約100ms
  • 同じ処理をもう一度当てても適用は0本で、管理テーブルの行数が20本分増えたまま fast-path で抜ける
  • 列の削除や型変更は20本のどこにもない

戻せない2本の中身

1本目は検索用のインデックスを新しい形で作り直す変更で、2本目は多言語サイトで言語ごとに分かれていた分類のデータを1行にまとめる変更です。どちらも適用前の形には戻せませんが、記事本文や設定の列が消えるわけではありません。リハーサルした2サイトは単一言語のため、2本目は1行も変わりませんでした。

戻す手段も設計する

戻す手段は2つに分けています。

  • コード: Worker を git revert で戻す
  • データ: D1 の Time Travel(Free プランでも直近7日分をデータベースごと分単位で復元できる機能)で戻す

関連

この記事をシェア