データベースマイグレーション
スキーマ変更を安全に本番へ適用するマイグレーションの手順と、ダウンタイムを避ける工夫を学ぶ
マイグレーションの基本
データベースのマイグレーションとは、 スキーマ(テーブル構造)の変更を、バージョン管理されたファイルとして記録し、 環境ごとに順番通り適用していく仕組みです。手作業でSQLを実行するのではなく、 変更履歴をコードとして残すことで、誰がいつどんな変更を行ったかを追跡できます。
-- migrations/0032_add_last_login_at_to_users.sql
ALTER TABLE users ADD COLUMN last_login_at TIMESTAMP NULL;
-- migrations/0032_add_last_login_at_to_users.down.sql (ロールバック用)
ALTER TABLE users DROP COLUMN last_login_at;ダウンタイムを避ける: expand-contractパターン
稼働中のシステムでカラム名の変更やデータ型の変更を行う場合、一度に切り替えようとすると アプリケーションとスキーマが一瞬でも不整合になりダウンタイムが発生しやすくなります。expand-contractパターンでは、 新旧の構造が一時的に共存できる状態を作り、段階的に移行します。
例: users.name を full_name にリネームしたい場合
1. expand(拡張)
ALTER TABLE users ADD COLUMN full_name VARCHAR(255) NULL;
-- アプリケーションは name を書き込みつつ、full_name にも同じ値を書き込む
2. migrate(移行)
UPDATE users SET full_name = name WHERE full_name IS NULL;
-- 既存データを新カラムへコピーする(バッチ処理で少しずつ実行)
3. アプリケーションの切り替え
-- 読み取りをfull_nameに切り替え、デプロイして問題ないことを確認する
3.5. 書き込みの切り替え
-- 書き込みをfull_nameのみに変更してデプロイし、nameへの書き込みが完全に無くなったことを確認する
4. contract(縮小)
ALTER TABLE users DROP COLUMN name;
-- 十分な期間を置いて問題がないことを確認してから、旧カラムを削除するなぜ一度に切り替えないのか
アプリケーションのデプロイとスキーマ変更は同時には反映されません。旧バージョンのアプリが まだ動いている間に新カラムだけを要求するようなスキーマ変更を行うと、その間のリクエストが 失敗してしまいます。新旧が共存できる期間を設けることで、無停止で移行できます。
ロールバック戦略
マイグレーション適用後に問題が見つかった場合に備え、あらかじめ元に戻す手順(downマイグレーション)を用意しておきます。 ただし、データを削除するような変更は単純に「戻す」ことができない場合があるため注意が必要です。
- カラムの追加はロールバックしやすいが、カラムの削除はデータが失われるため戻せない
- 本当に不要になったと確信できるまでは、カラムを即座に削除せず一定期間残しておく
- マイグレーションはステージング環境など本番相当のデータ量で事前検証してから適用する
大規模テーブルへの安全な変更
行数の多いテーブルに対する変更は、テーブル全体をロックして長時間の停止を招くことがあります。
-- 避けたい例: 巨大テーブルへの一括UPDATE(ロック時間が長くなりがち)
UPDATE orders SET status = 'archived' WHERE created_at < '2020-01-01';
-- 推奨: 小さなバッチに分けて実行し、都度スリープを挟む
-- (概念的な擬似コード)
while (残りの対象行がある) {
UPDATE orders SET status = 'archived'
WHERE created_at < '2020-01-01' AND status != 'archived'
LIMIT 1000;
sleep(100ms); // 他のクエリに実行機会を譲る
}データベース製品によっては、テーブルをロックせずにスキーマ変更を行う オンラインDDLの仕組みが提供されている場合もあります。大規模テーブルを扱う際は、 利用しているデータベースが提供する無停止変更の手段を事前に確認しておきます。
ポイント
- スキーマ変更はバージョン管理されたマイグレーションファイルとして記録する
- expand-contractパターンで新旧構造を一時共存させ、無停止で段階的に移行する
- ロールバック手順を用意しつつ、データを失う削除は慎重なタイミングで行う
- 大規模テーブルはバッチ分割やオンラインDDLでロック時間を抑える
確認クイズ
1 / 3expand-contractパターンの目的として正しいものはどれか?