データベースダウンマイグレーションの安全な使い方と危険な使い方

usersテーブルにphone_numberカラムを追加するマイグレーションをデプロイしたとします。数時間後、カラム名はコードベースの他の部分に合わせてphoneにすべきだったと誰かが気づきました。最初に思いつくのはダウンマイグレーションを実行してカラムを削除し、正しい名前で再デプロイすることです。簡単ですよね?

開発初期であれば、それで問題ありません。しかし本番環境では、同じ行動が顧客データの損失、アプリケーションエラーの誘発、そして解決に数日を要する混乱を引き起こす可能性があります。

ダウンマイグレーションは、データベースにおける「元に戻す」操作に相当します。マイグレーションでカラムを追加した場合、ダウンマイグレーションはそのカラムを削除します。テーブルを作成した場合は、ダウンマイグレーションでそのテーブルを削除します。概念は単純に聞こえますが、実際のユーザーと実際のデータが関わると、その結果は決して単純ではありません。

開発初期におけるダウンマイグレーションの安全性

チームがブランチ上で新機能を開発しているとき、スキーマは1日に何度も変更される可能性があります。マイグレーションを書き、テストし、アプローチが間違っていることに気づき、ダウンマイグレーションを実行します。ユーザーがいないため、誰も影響を受けません。隔離された環境で作業しているため、他のコードがそのスキーマに依存することもありません。

ここがダウンマイグレーションの真価を発揮する場面です。クリーンアップを気にせずに素早く実験できます。異なるカラム型を試したり、テーブル構造をテストしたり、高速にイテレーションできます。まだ永続的なものは何も存在しないため、ミスのコストはゼロです。

ステージング環境で顕在化する最初のリスク

ステージング環境はグレーゾーンに位置します。ここでもダウンマイグレーションは機能しますが、危険な側面が見え始めます。

問題はデータです。ステージング環境には、匿名化されたバックアップから、またはテスト中の実際の使用から得られた、本番環境に近いデータが含まれていることがよくあります。ダウンマイグレーションでカラムを削除すると、そのカラムにあったデータはすべて失われます。ステージングでは通常データを再読み込みできますが、そのプロセスには時間がかかります。数百万行のテーブルを再構築するには数時間かかる可能性があります。

さらに重要なのは、ステージングが習慣を形成することです。チームが毎日ステージングでダウンマイグレーションを実行することに慣れてしまうと、その筋肉記憶が本番環境にも持ち越されます。ステージングでは無害だった同じ操作が本番環境では破壊的になり、誰も「いつもこうやっているから」と立ち止まって考えなくなります。

本番環境:ダウンマイグレーションが危険になる場所

本番環境こそ、「元に戻す」という単純な概念が崩壊する場所です。3つの具体的な問題により、本番環境でのダウンマイグレーションはリスクが高くなります。

次の状態図は、ダウンマイグレーションの安全性が環境によってどのように変化するかを示しています。

flowchart TD Dev[開発] -->|ユーザーなし、データリスクなし| Staging[ステージング] Staging -->|本番類似データ、習慣形成| Prod[本番] Prod -->|データ損失| DataLoss[データ損失は永続的] Prod -->|コードとスキーマの不一致| Sync[コードとスキーマの同期不全] Prod -->|元に戻せない変更| Irreversible[元に戻せない変更もある]

usersテーブルにphone_numberカラムを追加した次のマイグレーションを考えてみましょう。

-- Up migration
ALTER TABLE users ADD COLUMN phone_number varchar(20);

-- Down migration
ALTER TABLE users DROP COLUMN phone_number;

ユーザーがすでに電話番号を入力していた場合、ダウンマイグレーションを実行するとそのデータは即座に破壊されます。警告も確認も元に戻すこともできません。カラムとそのすべての値が消えます。

データ損失は永続的

カラムを削除するダウンマイグレーションを実行すると、そのカラムのすべての値が失われます。データベースのカラムにゴミ箱はありません。マイグレーションでphone_numberカラムを追加し、ユーザーがすでに番号を入力していた場合、カラムが削除されるとそれらの番号は消えます。

「バックアップから復元すればいい」と思うかもしれません。しかし、マイグレーション実行後に取得したバックアップには、新しいデータを含む新しいカラムがすでに含まれています。マイグレーション実行前のバックアップから復元すると、マイグレーション実行後に行われたすべての変更が失われます。どちらにせよ、データは失われます。

唯一安全なアプローチは、マイグレーション実行前のバックアップから復元し、その後、問題のマイグレーションを除く、マイグレーション実行後に発生したすべての変更を再生することです。このプロセスは複雑で、時間がかかり、エラーが発生しやすいです。ほとんどのチームは、これを確実に実行するためのツールや運用規律を持っていません。

コードとスキーマの同期が取れなくなる

これはダウンマイグレーションにおける最も一般的な本番障害パターンです。マイグレーションでordersテーブルにデフォルト値pendingstatusカラムを追加したとします。新しいアプリケーションコードはこのカラムを読み取ります。ダウンマイグレーションを実行すると、カラムは消えます。

しかし、アプリケーションインスタンスはまだ新しいコードを実行しています。期待するカラムが存在しないため、すぐにエラーを吐き始めます。アプリケーションコードのロールバックを開始しても、ロールバックは瞬時には完了しません。複数のインスタンスがあり、それぞれに独自のデプロイサイクルがあります。一部のインスタンスは新しいコードを実行し続け、他のインスタンスはロールバックされている可能性があります。その間、エラーがシステム全体に連鎖します。

根本的な問題は、アプリケーションのロールバックとデータベースのロールバックを完全に同期させることができないことです。コードが存在しないスキーマを期待する期間、またはスキーマに古いコードが処理方法を知らないカラムが存在する期間が常に存在します。

元に戻せない変更もある

特定のマイグレーションは本質的に破壊的です。first_namelast_nameを1つのfull_nameカラムに結合するマイグレーションを考えてみましょう。元のデータは変換されています。ダウンマイグレーションを実行するとfirst_namelast_nameカラムを再作成できますが、その中のデータはマイグレーション実行前のものと一致しません。元の分離は失われます。

別の例:レガシークエリでまだ使用されていたカラムを削除するマイグレーション。そのカラムが削除されると、データは失われます。ダウンマイグレーションの魔法でそれを復元することはできません。唯一の復旧方法はバックアップからの復元ですが、これにより前述のすべての問題が再発します。

本番環境でダウンマイグレーションが許容されるケース

ダウンマイグレーションが本番環境で普遍的に禁止されているわけではありません。安全に使用できる特定の条件があります。

  • マイグレーションが、データが一度も投入されたことのない新しいテーブルまたはカラムのみを追加する場合。
  • 新しいスキーマに依存するアプリケーションコードがまだデプロイされていない場合。
  • 変更されたスキーマを参照する実行中のプロセス、スケジュールされたジョブ、バックグラウンドワーカーが存在しないことを確認できる場合。

これらの場合でも、最も安全なアプローチは、ダウンマイグレーションを新しいフォワードマイグレーションとして扱うことです。変更を明示的に元に戻すマイグレーションを書き、デプロイし、通常のパイプラインを通じて実行させます。これにより、ダウンマイグレーションと同じ結果が得られますが、完全な可視性、テスト、ロールバック機能が備わっています。

本番環境でダウンマイグレーションを実行する前の実用的なチェックリスト

ダウンマイグレーションを実行する前に、次の質問を自問してください。

  • 削除されるカラムまたはテーブルにユーザーデータはありますか?
  • このスキーマに依存するコードをまだ実行しているアプリケーションインスタンスはありますか?
  • 変更されたオブジェクトを参照するバックグラウンドジョブ、スケジュールされたタスク、データパイプラインはありますか?
  • マイグレーション実行後に入力された情報を失うことなく変更を元に戻せますか?
  • マイグレーション実行前に取得した検証済みのバックアップはありますか?
  • 大規模なテーブルでダウンマイグレーションが実行されている間のダウンタイムに対応できますか?

最初の3つの質問のいずれかに「はい」と答えた場合は、ダウンマイグレーションを実行しないでください。代わりにフォワードマイグレーションを書いてください。

より安全な代替案:後退ではなく前進する

本番環境で問題のあるデータベースマイグレーションを修正するための最も信頼性の高い戦略は、元に戻すことではなく、前進して修正することです。問題を修正する新しいマイグレーションを書きます。カラム名が間違っている場合は、正しいカラムを追加し、データをコピーし、古いカラムを非推奨にします。スキーマ変更でバグが発生した場合は、スキーマを正しい状態に調整するマイグレーションを追加します。

フォワードマイグレーションは、既存のデータを保持し、実行中のコードとの互換性を維持し、他のすべての変更と同じデプロイプロセスに従うため、より安全です。アプリケーションとデータベースのロールバック間の完全な同期を必要としません。エラーがシステム全体に伝播する不整合のウィンドウを作成しません。

ダウンマイグレーションは開発ツールです。本番環境では、後退よりも前進の方が常に安全です。