戻せない 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日分をデータベースごと分単位で復元できる機能)で戻す