保守 レッスン4

データベースマイグレーション

スキーマ変更を安全に本番へ適用するマイグレーションの手順と、ダウンタイムを避ける工夫を学ぶ

マイグレーションの基本

データベースのマイグレーションとは、 スキーマ(テーブル構造)の変更を、バージョン管理されたファイルとして記録し、 環境ごとに順番通り適用していく仕組みです。手作業で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 / 3

expand-contractパターンの目的として正しいものはどれか?