ダウンタイムなしでテーブル名を変更
GitLab 13.12 で導入されました。
GitLabに組み込まれたデータベースヘルパーメソッドを使えば、ダウンタイムなしでデータベースのテーブル名を変更することができます。
このテクニックはデータベースのビューの上に構築され、以下のステップを使います:
- データベーステーブルの名前を変更します。
- 古いテーブル名を使用して、新しいテーブル名を指すデータベース・ビューを作成します。
- ActiveRecordのスキーマ・キャッシュの回避策を追加しました。
例えば、issues
のテーブル名をtickets
に変更するとします。実行します:
BEGIN;
ALTER TABLE issues RENAME TO tickets;
CREATE VIEW issues AS SELECT * FROM tickets;
COMMIT;
データベースビューは、基本的なテーブルスキーマ(デフォルト値、NULL制約、インデックス)を公開しないので、新しいテーブル名を使用するようにアプリケーションを更新するには、さらなる手順が必要です。ActiveRecordは、新しいモデルを初期化するなど、このデータに大きく依存しています。
この制限を回避するには、新しいテーブル名を使用して、別のテーブルからこの情報を取得するようにActiveRecordに指示する必要があります。
マイグレーション戦略の内訳
リリースN.M: ActiveRecordモデルのテーブルにマークを付けます。
現在のリリースを「リリースN.M」と考えてください。
このリリースでは、データベース・テーブルを登録し、ActiveRecordに新しいテーブル名(存在する場合)を使用してデータベース・テーブル情報(SchemaCache
)を取得するように指示します。そうでない場合は、古いテーブル名にフォールバックします。これは、ゼロダウンタイムのデプロイ時のエラーを回避するために必要です。
-
で
TABLES_TO_BE_RENAMED
定数を編集します:lib/gitlab/database.rb
TABLES_TO_BE_RENAMED = { 'issues' => 'tickets' }.freeze
このリリース(N.M)では、tickets
データベース・テーブルはまだ存在しないことに注意してください。このステップは、リリースN.M+1での実際のテーブル名変更の準備です。
リリースN.M+1:データベース・テーブル名の変更
次のリリースを「リリースN.M」と考えてください。
標準マイグレーションを実行します(ポストマイグレーションではありません):
enable_lock_retries!
def up
rename_table_safely(:issues, :tickets)
end
def down
undo_rename_table_safely(:issues, :tickets)
end
重要な注意事項です:
- テーブル名が変更されることを他の開発者に知らせましょう。
- マージリクエストで
@gl-database
グループに Ping を送ってください。 - Engineering Week-in-Review ドキュメントに注記を追加してください:
table_name
はN.M.で名前が変更される予定です。このテーブルの変更はリリースN.M.およびN.M+1では許可されません。
- マージリクエストで
- ヘルパーメソッドでは、テーブル名の変更にRailsの標準ヘルパー
rename_table
。 - このヘルパーはシーケンスとインデックスの名前を変更します。インデックスの名前を付けるときにRailsの標準的な規約から外れることがあるので、すべてのインデックスの名前が正しく変更されない可能性があります。マイグレーションをローカルで実行した後、矛盾した名前のインデックス (
db/structure.sql
) がないか確認してください。これらは別のマイグレーションで手動で名前を変更することができ、リリースM.N+1の一部とすることもできます。 - 外部キーカラムには古いテーブル名が残っている可能性があります。小さいテーブルの場合は、標準の列名変更プロセスに従ってください。
- トリガを使用しているデータベーステーブルの名前の変更は避けてください。
- テーブルの変更(カラムの追加や削除)は、名前の変更プロセス中には許可されません。テーブルへのすべての変更は、名前の変更がマイグレーションを開始する前(または次のリリース)に行われるようにしてください。
- インデックス名が変更される可能性があるため、モデルが
unique_by: index_name
オプションで一括挿入(例えば、insert_all
やupsert_all
)を使用していないことを確認してください。これらの方法を使用している時にインデックス名を変更すると、機能が破壊される可能性があります。 - 新しいデータベーステーブルを指すようにモデルコードを修正します。モデルの名前を直接変更するか、
self.table_name
変数を設定します。
この時点では、クエリで古いデータベーステーブル名を使用するアプリケーションはありません。
-
マイグレーション後にデータベースビューを削除します:
def up finalize_table_rename(:issues, :tickets) end def down undo_finalize_table_rename(:issues, :tickets) end
-
さらに、
TABLES_TO_BE_RENAMED
からのテーブル定義も削除する必要があります。
そのためには、lib/gitlab/database.rb
のTABLES_TO_BE_RENAMED
定数を編集してください:
を変更することを忘れないでください:
TABLES_TO_BE_RENAMED = {
'issues' => 'tickets'
}.freeze
To:
TABLES_TO_BE_RENAMED = {}.freeze
ゼロダウンタイムのデプロイ
アプリケーションをダウンタイムなしでアップグレードする場合、古いコードを実行しているアプリケーションインスタンスが存在する可能性があります。古いコードはまだ古いデータベーステーブルを参照しています。後方互換性のあるデータベースビューが存在するため、クエリは問題なく機能します。
古いバージョンのアプリケーションを再起動したり、データベースに再接続する必要がある場合、ActiveRecordはカラム情報を再度取得します。このとき、以前にマークしたテーブル(TABLES_TO_BE_RENAMED
)は、データベーステーブル情報をフェッチするときに新しいデータベーステーブル名を使用するようにActiveRecordに指示します。
新しいバージョンのアプリケーションでは、新しいデータベーステーブルが使用されます。