ダウンタイムなしでテーブル名を変更

GitLab 13.12 で導入されました

GitLabに組み込まれたデータベースヘルパーメソッドを使えば、ダウンタイムなしでデータベースのテーブル名を変更することができます。

このテクニックはデータベースのビューの上に構築され、以下のステップを使います:

  1. データベーステーブルの名前を変更します。
  2. 古いテーブル名を使用して、新しいテーブル名を指すデータベース・ビューを作成します。
  3. 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 )を取得するように指示します。そうでない場合は、古いテーブル名にフォールバックします。これは、ゼロダウンタイムのデプロイ時のエラーを回避するために必要です。

  1. 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_allupsert_all)を使用していないことを確認してください。これらの方法を使用している時にインデックス名を変更すると、機能が破壊される可能性があります。
  • 新しいデータベーステーブルを指すようにモデルコードを修正します。モデルの名前を直接変更するか、self.table_name 変数を設定します。

この時点では、クエリで古いデータベーステーブル名を使用するアプリケーションはありません。

  1. マイグレーション後にデータベースビューを削除します:

      def up
        finalize_table_rename(:issues, :tickets)
      end
       
      def down
        undo_finalize_table_rename(:issues, :tickets)
      end
    
  2. さらに、TABLES_TO_BE_RENAMED からのテーブル定義も削除する必要があります。

そのためには、lib/gitlab/database.rbTABLES_TO_BE_RENAMED 定数を編集してください:

を変更することを忘れないでください:

TABLES_TO_BE_RENAMED = {
  'issues' => 'tickets'
}.freeze

To:

TABLES_TO_BE_RENAMED = {}.freeze

ゼロダウンタイムのデプロイ

アプリケーションをダウンタイムなしでアップグレードする場合、古いコードを実行しているアプリケーションインスタンスが存在する可能性があります。古いコードはまだ古いデータベーステーブルを参照しています。後方互換性のあるデータベースビューが存在するため、クエリは問題なく機能します。

古いバージョンのアプリケーションを再起動したり、データベースに再接続する必要がある場合、ActiveRecordはカラム情報を再度取得します。このとき、以前にマークしたテーブル(TABLES_TO_BE_RENAMED)は、データベーステーブル情報をフェッチするときに新しいデータベーステーブル名を使用するようにActiveRecordに指示します。

新しいバージョンのアプリケーションでは、新しいデータベーステーブルが使用されます。