Safe Schema Migration
前提
アプリケーションのために前方互換にできないならアプリケーションコードを変更しなければいけない
CREATE TABLE 🟢
気にせず実行可能
ただし外部キーを追加する場合は少し注意
DROP TABLE 🔴
危険。手順が重要
既存のソースを改修し、該当テーブルを参照しないようにする
ローカル・テスト環境でテーブルを削除し、エラーにならないことを確認する
いきなり削除せずに、まずはテーブルのrenameを行い数日様子を見る → 問題が発生すれば、再度renameをして元に戻す
テーブル削除のmigationを適用する
ALTER TABLE 🟡🔴
ALTER TABLEを行うトランザクションをなるべく短くする、という要点は製品によらず同じ
PostgreSQL
基本的にテーブルレベルのACCESS EXCLUSIVEロックを取得します。これは他のどのロックとも競合するとても強いロックです。つまりALTER TABLEを実行してトランザクションが完了するまでは、INSERTやUPDATEはもちろんSELECTさえ待機する MySQL
5.5以前
すべての ALTER TABLE は実行中にテーブルの共有ロックを取得する。従って実行中にデータの読み取りはできるが書き込み(INSERT, UPDATE, DELETE)はできない。ロックしている間に既存テーブルのデータを新しいテーブルにコピーして最後にテーブルを置き換える。 5.6以降
オンラインDDLによりテーブルのデータコピーが不要なALTER TABLEが増えた 実行中に書き込みできる操作も増えた
気をつける点
複数テーブルにALTER TABLEする場合COMMITするまで、すでに処理が完了していて関係ないテーブルもロックが解放されない
ALTER TABLE - RENAME TABLE 🔴
危険、rename_tableを行うとアプリケーションにとって非互換なのでdowntimeが発生する
ALTER TABLEと同時にVIEWを作成して互換性を担保するアプローチ
Railsのmigrationを少し拡張したヘルパーを実装している
ALTER TABLE - ADD COLUMN 🟡
基本的には安全
デフォルト制約には要注意
add_column(:account, :balane, :integer, {:null=>false, :default=>0})を実行するとSELECT LOCKがかかる
実行中に書き込みはできるが、テーブルコピーが発生する
MySQL 8.0.12以降は安全
大量データのあるテーブルに対するクエリが滞留してしまう可能性
手順
null: trueでカラムを追加
新規追加レコードにはdefault値を埋めるようにしたアプリケーションコードをデプロイ
既存データを埋めるデータマイグレーションを行う
null: falseにALTER TABLEする
ALTER TABLE - REMOVE COLUMN 🔴
最も危険な操作の一つ
アプリケーションコード上で対象カラムを参照していなくてもRailsが機械的に全カラムを取得するスキーマキャッシュにより削除対象カラムを見に行く可能性がある eager_loadするときによく見るt0_r0みたいな別名を振っているやつ
ignored_columnsを使う
手順
アプリケーションコード: そのカラムを参照している箇所を直す
アプリケーションコード: Model.ignored_columns にカラム名を追加する
デプロイ(上記2つを同時でも別でもよい)
マイグレーション: 実際にカラムを削除
アプリケーションコード: Model.ignored_columns を元に戻す
デプロイ
ALTER TABLE - RENAME COLUMN 🔴
REMOVE COLUMNと同じ観点、手順
カラム定義の変更がある場合はADD COLUMNと同じ観点、手順
ALTER TABLE - ADD FOREIGN KEY 🔴
参照先のテーブルに正しく存在するか全てのレコードが検査されるため、大量のレコードがある場合にトランザクションが長くなる=ロックが長時間かかる
一度に複数の外部キーを追加するとそのぶん長くなる
PostgreSQLでの安全な手順
ADD CONSTRAINT構文のNOT VALIDオプションを使うことで検査をスキップできる
そのあとにVALIDATE CONSTRAINT構文を実行する
SHARE UPDATE EXCLUSIVEロックなのでALTER TABLEやVACUUMコマンドとしか競合しない弱いロックになり、通常のINSERT/UPDATE/SELECTは問題なく利用することができる
ALTER TABLE - CREATE INDEX 🟡
PostgreSQL
SHAREロックを獲得する
大量レコードがありインデックス作成に時間がかかる場合、ROW EXCLUSIVEロックが必要なINSERT/UPDATEが長時間滞留する
CONCURRENTLYオプションを利用し、SHAREロックを獲得しないようにする
ALTER TABLE - MODIFY ENUM 🟡
MySQL
ストレージサイズが変更されない限り、一瞬で完了する
ただしリストの途中に追加すると、テーブルコピーが発生するので注意
code:sql
ALTER TABLE my_table MODIFY my_column ENUM ('a', 'b'); -- ←のようなENUM定義のあとに...
ALTER TABLE my_table MODIFY my_column ENUM ('a', 'b', 'c'); -- 末尾追加は高速
ALTER TABLE my_table MODIFY my_column ENUM ('a', 'c', 'b'); -- リスト中への追加は低速
関連
参考