ひとことで言うと#
データベースのスキーマ変更(テーブル追加・カラム変更など)をコードとしてバージョン管理し、順序通りに適用・巻き戻しできるようにする手法。「本番DBに手動でALTER TABLE」という危険な運用から脱却する。
押さえておきたい用語#
- マイグレーションファイル(Migration File)
- スキーマ変更の内容を記述したバージョン管理されたファイルのこと。タイムスタンプ+説明の命名規則(例:
20260309_add_email_to_users.sql)で順序を保証する。 - UP / DOWN
- マイグレーションの**適用(UP)と巻き戻し(DOWN)**のペアのこと。UPでカラム追加→DOWNでカラム削除のように、すべての変更にロールバック手順を用意する。
- オンラインDDL(Online DDL)
- 大規模テーブルのスキーマ変更をテーブルロックなしで実行するツールや手法のこと。gh-ost、pt-online-schema-changeなどが代表例。
- ゼロダウンタイムマイグレーション
- サービスを停止せずにスキーマ変更を完了させる手法のこと。通常2〜3段階のデプロイに分けて、アプリの変更とDB変更を段階的に適用する。
データベースマイグレーションの全体像#
こんな悩みに効く#
- 本番DBのスキーマが開発環境と微妙にずれていて、デプロイ時にエラーが出る
- 誰がいつどんなスキーマ変更をしたか追跡できない
- スキーマ変更を手動で本番に適用しており、ミスが怖い
基本の使い方#
プロジェクトの技術スタックに合ったマイグレーションツールを選択する。
- Flyway: Java系プロジェクトで定番。SQLベースでシンプル
- Liquibase: XML/YAML/JSONでスキーマ変更を定義。DB非依存
- Alembic: Python(SQLAlchemy)向け
- golang-migrate: Go向け。SQLファイルベース
- 各フレームワーク内蔵: Rails Migrations、Django Migrations、Laravel Migrations
ポイント: SQLベースのツールはDBに依存しない記述が難しいが、チームが理解しやすい。
チームで一貫したルールを定める。
- ファイル命名: タイムスタンプ + 説明(例:
20260309120000_add_email_to_users.sql) - 1ファイル1変更: 1つのマイグレーションには関連する変更だけを含める
- ロールバック: UPとDOWNの両方を必ず書く(戻せない変更は例外として明記)
- レビュー: マイグレーションファイルもコードレビューの対象にする
ポイント: マイグレーションの順序は変更しない。途中に挿入すると環境間で不整合が起きる。
本番稼働中のDBに対してダウンタイムなしで変更を適用するパターンを身につける。
- カラム追加: NULLableで追加 → アプリケーション対応 → NOT NULLに変更
- カラム削除: アプリケーションから参照を除去 → 次のリリースでカラム削除
- テーブルリネーム: 新テーブル作成 → データコピー → アプリ切り替え → 旧テーブル削除
- 大量データの変更: バッチ処理で少しずつ変更(ロック競合を防ぐ)
ポイント: 破壊的な変更は最低2段階のデプロイに分ける。アプリの変更とDB変更を同時にやらない。
マイグレーションを自動化パイプラインに統合する。
- CIで: マイグレーションがテスト環境で正常に適用されることを検証
- CD(デプロイ時)で: アプリケーションデプロイ前にマイグレーションを自動実行
- ロールバック計画: 失敗時に前バージョンに戻す手順を事前に用意
ポイント: マイグレーションは必ずアプリのデプロイより先に実行する。順序を逆にするとアプリがスキーマ不整合で落ちる。
具体例#
状況: usersテーブル(300万行)にemailカラムを追加したい。本番は24時間稼働でダウンタイム不可。
Phase 1 - カラム追加(マイグレーション): ALTER TABLE users ADD COLUMN email VARCHAR(255) NULL; NULLableで追加するので、既存のコードに影響なし。
Phase 2 - アプリケーション対応: 新コードはemailカラムの読み書きに対応。既存レコードのemailはNULLでもエラーにならない設計。バックグラウンドジョブで既存ユーザーのemail移行を実行。
Phase 3 - 制約追加(全データ移行完了後): ALTER TABLE users ALTER COLUMN email SET NOT NULL; + UNIQUE制約を追加。
結果: ダウンタイムゼロでスキーマ変更完了。各フェーズの間隔は1〜2日空けて安全を確認。
問題: ordersテーブル(5億行、200GB)に新しいインデックスを追加したい。通常のCREATE INDEXではテーブルロックが推定4時間かかり、その間注文処理が停止する。
対策: gh-ost(GitHub Online Schema Tool)を使用。元テーブルのコピーを作成し、binlogを使ってリアルタイムに差分を同期。コピー完了後にアトミックにテーブルを入れ替え。
実行: gh-ost --alter="ADD INDEX idx_user_date(user_id, created_at)" を実行。バックグラウンドで6時間かかったが、その間のサービスへの影響はCPU使用率+5%程度。ダウンタイムはゼロ。
結果: オンラインDDLの導入により、大規模テーブルのスキーマ変更が「営業時間中でも安全」に実行可能になった。
状況: ステージング環境でのマイグレーションは成功したが、本番環境でデータ量の違いからタイムアウトで失敗。デプロイパイプラインが中断。
自動ロールバックの仕組み:
- マイグレーション実行前にスナップショットを自動取得
- マイグレーション実行(タイムアウト制限: 30分)
- 失敗時に自動でDOWNスクリプトを実行
- DOWNも失敗した場合はスナップショットからリストア
- Slackに即座にアラートを送信
結果: マイグレーション失敗から5分以内に自動ロールバックが完了。以前は手動対応で平均45分かかっていた障害対応が90%短縮。
やりがちな失敗パターン#
- ロールバックスクリプトを書かない — 問題が発生した時に手動で戻すのは危険でミスが起きやすい。すべてのマイグレーションにDOWNスクリプトを用意する
- 大テーブルにALTER TABLEを一発で実行する — 数億行のテーブルへのALTER TABLEはロックがかかり、サービスが止まる。オンラインDDLツール(gh-ost、pt-online-schema-change)を使う
- マイグレーションとアプリデプロイを同時にする — スキーマ変更とアプリの変更を同じデプロイで実施すると、失敗時の切り戻しが複雑になる。マイグレーションは独立したステップとして実行する
- テスト環境でのマイグレーション検証を省略する — 本番で初めて実行して失敗するケースが多い。CIで自動的にマイグレーションの適用を検証する仕組みを組み込む
まとめ#
データベースマイグレーションは 「DBの変更履歴をコードで管理する」 という、現代のソフトウェア開発に不可欠なプラクティス。手動のALTER TABLEから脱却し、バージョン管理されたマイグレーションファイルで安全にスキーマを進化させよう。本番環境では「2段階デプロイ」と 「オンラインDDL」 を組み合わせて、ゼロダウンタイムを実現することが成功の鍵だ。