バックグラウンドでの一括マイグレーション

バッチバックグラウンドマイグレーションは、マイグレーションがガイドラインの制限時間を超える場合に、データマイグレーションを実行するために使用します。たとえば、1つのJSONカラムに格納されているデータを別のテーブルにマイグレーションする場合は、バッチバックグラウンドマイグレーションを使用します。

note
バッチバックグラウンドマイグレーションは、従来のバックグラウンドマイグレーションフレームワークに取って代わりました。そのフレームワークに関わる変更については、そのドキュメントを確認してください。
note
バッチバックグラウンドマイグレーションフレームワークはChatOpsをサポートしています。ChatOpsを使うことで、GitLabエンジニアはシステムに存在するバッチドバックグラウンドマイグレーションと対話することができます。

バッチバックグラウンドマイグレーションを使うタイミング

非常に多くの行を含むテーブルの_データを_マイグレーションする際に、通常のRailsマイグレーションを使用するとガイドラインの制限時間を超えてしまう場合に、バッチバックグラウンドマイグレーションを使用します。

  • バッチのバックグラウンドマイグレーションは、トラフィックの多いテーブルのデータをマイグレーションするときに使用してください。
  • バッチのバックグラウンドマイグレーションは、大規模なデータセットの各項目に対して多数の単一行クエリを実行する場合にも使用できます。通常、単一レコードパターンの場合、実行時間はデータセットのサイズに大きく依存します。それに応じてデータセットを分割し、バックグラウンドマイグレーションに入れます。
  • スキーママイグレーションを実行するためにバッチバックグラウンドマイグレーションを使用しないでください。

バックグラウンドマイグレーションは次のような場合に役立ちます:

  • イベントを1つのテーブルから複数のテーブルにマイグレーションする場合。
  • 別のカラムに格納された JSON に基づいて、1 つのカラムにデータを入力します。
  • 外部サービスの出力に依存するデータのマイグレーション。(たとえば API。)

備考

  • バッチバックグラウンドマイグレーションが重要なアップグレードの一部である場合、リリースポストで発表する必要があります。マイグレーションがこのカテゴリーに該当するかどうか不明な場合は、プロジェクトマネージャーと相談してください。
  • 必要なファイルがデフォルトで作成されるように、ジェネレーターを使用してバッチバックグラウンドマイグレーションを作成する必要があります。

バッチバックグラウンドマイグレーションの仕組み

バッチバックグラウンドマイグレーション(BBM) はGitlab::BackgroundMigration::BatchedMigrationJob のサブクラスで、perform メソッドを定義します。最初のステップとして、通常のマイグレーションは、batched_background_migrations BBMクラスと必要な引数を持つレコードを batched_background_migrations作成します。batched_background_migrations デフォルトでは、 batched_background_migrationsマイグレーションはアクティビティ状態にあり、それらは実際のバッチマイグレーションを実行するためにSidekiqワーカーによってピックアップされます。

すべてのマイグレーションクラスは、名前空間Gitlab::BackgroundMigration で定義する必要があります。ファイルをディレクトリlib/gitlab/background_migration/ に配置します。

実行メカニズム

バッチ処理されたバックグラウンドマイグレーションは、キューに入れられた順番にキューから取り出されます。複数のマイグレーションは、それらがアクティビティ状態にあり、同じデータベーステーブルをターゲットにしていない限り、フェッチされ、並列に実行されます。GitLab.comの場合、並行して処理されるマイグレーションのデフォルトの数は2つで、この制限は4つに設定されています。マイグレーションが実行用に選択されると、特定のバッチ用にジョブが作成されます。各ジョブの実行後、マイグレーションのバッチサイズは、直近の20ジョブのパフォーマンスに基づいて増減することができます。

ワーカーが利用可能になり次第、BBMはランナーによって処理されます。

べき等

バッチバックグラウンドマイグレーションは、Sidekiqプロセスのコンテキストで実行されます。通常のSidekiqルールが適用され、特にジョブは小さく、べき等であるというルールが適用されます。マイグレーションジョブが再試行される場合、データの整合性が保証されることを確認してください。

詳細については、Sidekiqベストプラクティスガイドラインを参照してください。

マイグレーション最適化

各ジョブの実行後、マイグレーションを最適化できるかどうかの検証が行われます。最適化の基礎となる仕組みは、時間効率の概念に基づいています。直近のNジョブの時間効率の指数移動平均を計算し、バッチバックグランドマイグレーションのバッチサイズを最適値に更新します。

ジョブ再試行メカニズム

バッチバックグラウンドマイグレーションのリトライメカニズムは、失敗した場合にジョブが再度実行されることを保証します。次の図は、リトライ機構のさまざまなステージを示しています:

失敗したバッチバックグラウンドマイグレーション

以下のいずれかが真である場合、バッチバックグラウンドマイグレーション全体はfailed とマークされます (/chatops run batched_background_migrations status MIGRATION_IDfailed としてマイグレーションを表示します ):

バッチマイグレーションのスロットリング

バッチマイグレーションは更新負荷が高く、過去にはデータベースのパフォーマンスが低下している間にマイグレーションによる高負荷が原因で発生したインシデントがほとんどなかったため、それを軽減するためのスロットリング機構が存在します。

これらのデータベース指標はマイグレーションをスロットルするためにチェックされます。停止シグナルを受け取ると、マイグレーションは設定された時間(10分間)一時停止されます:

  • アーカイブ保留中のWALキューがしきい値を超えました。
  • マイグレーションが動作するテーブルのアクティブなオートバキューム。
  • Patroni apdex SLIがSLOを下回っています。
  • WALレートが閾値を超えました。

データベースの健全性チェック・フレームワークをさらに強化するために、より多くの指標を追加するための継続的な取り組みです。詳細はエピック7594を参照してください。

分離

バッチバックグラウンドマイグレーションは分離する必要があり、アプリケーションコード(たとえば、ApplicationRecord クラス以外のapp/models で定義されたモデル)を使用することはできません。これらのマイグレーションは実行に長い時間がかかるため、マイグレーションが実行されている間に新しいバージョンがデプロイされる可能性があります。

マイグレーションされたデータによって

通常のマイグレーションやポストマイグレーションとは異なり、次のリリースを待つだけでは、データが完全に移行されたことを保証することはできません。つまり、BBMが終了するまでデータに依存すべきではありません。100%移行されたデータが必要な場合は、ensure_batched_background_migration_is_finished ヘルパーを使用して、マイグレーションが終了し、データが完全に移行されたことを保証することができます。(例を見てください)。

サンプル・プロジェクトの作成方法

バッチバックグランドマイグレーションを生成する方法

カスタムジェネレータbatched_background_migration は必要なファイルを雛形化し、引数としてtable_name,column_name,feature_category を受け取ります。使い方

bundle exec rails g batched_background_migration my_batched_migration --table_name=<table-name> --column_name=<column-name> --feature_category=<feature-category>

このコマンドは以下のファイルを作成します:

  • db/post_migrate/20230214231008_queue_my_batched_migration.rb
  • spec/migrations/20230214231008_queue_my_batched_migration_spec.rb
  • lib/gitlab/background_migration/my_batched_migration.rb
  • spec/lib/gitlab/background_migration/my_batched_migration_spec.rb

バッチバックグラウンドマイグレーションをエンキューします。

バッチ化されたバックグラウンドマイグレーションをキューに入れることは、デプロイ後のマイグレーションで行うべきです。マイグレーションがバッチで実行されるようにキューイングする例として、queue_batched_background_migration 。クラス名と引数をマイグレーションの値に置き換えてください:

queue_batched_background_migration(
  JOB_CLASS_NAME,
  TABLE_NAME,
  JOB_ARGUMENTS,
  JOB_INTERVAL
  )
note
提供されたジョブ引数の数がJOB_CLASS_NAME で定義されたジョブ引数の数と一致しない場合、このヘルパーはエラーを発生させます。

新しく作成されたデータがマイグレーションされるか、作成時に古いバージョンと新しいバージョンの両方で保存されることを確認してください。削除はカスケード削除の外部キーを定義することで処理できます。

ジョブ引数の使用

BatchedMigrationJob はジョブクラスが必要とするジョブ引数を定義するためにjob_arguments ヘルパーメソッドを提供します。

queue_batched_background_migration でスケジュールされたバッチマイグレーションはジョブ引数を定義するためにヘルパーを使わなければなりません

queue_batched_background_migration(
  'CopyColumnUsingBackgroundMigrationJob',
  TABLE_NAME,
  'name', 'name_convert_to_text',
  job_interval: DELAY_INTERVAL
)

queue_batched_background_migration 定義されたジョブ引数の数がマイグレーションをスケジューリングするときに提供されたジョブ引数の数と一致しない場合、queue_batched_background_migration はエラーを発生します。

この例では、copy_fromname を返し、copy_toname_convert_to_text を返します:

class CopyColumnUsingBackgroundMigrationJob < BatchedMigrationJob
  job_arguments :copy_from, :copy_to
  operation_name :update_all

  def perform
    from_column = connection.quote_column_name(copy_from)
    to_column = connection.quote_column_name(copy_to)

    assignment_clause = "#{to_column} = #{from_column}"

    each_sub_batch do |relation|
      relation.update_all(assignment_clause)
    end
  end
end

フィルタを使用

デフォルトでは、マイグレーションを実行するバックグラウンドジョブを作成する場合、バッチドバックグラウンドマイグレーションは指定されたテーブル全体を繰り返し処理します。この反復はPrimaryKeyBatchingStrategy を使って行われます。 テーブルに1000レコードあり、バッチサイズが100の場合、ジョブは10回に分割されます。説明のために、EachBatch はこのように使用されます:

# PrimaryKeyBatchingStrategy
Namespace.each_batch(of: 100) do |relation|
  relation.where(type: nil).update_all(type: 'User') # this happens in each background job
end

場合によっては、レコードのサブセットのみを検査する必要があります。1000レコードのうち10%だけを検査する必要がある場合は、ジョブの作成時に最初のリレーションにフィルタを適用します:

Namespace.where(type: nil).each_batch(of: 100) do |relation|
  relation.update_all(type: 'User')
end

最初の例では、各バッチで何件のレコードが更新されるかわかりません。最初の例では、各バッチで何件のレコードが更新されるかはわかりません。

BatchedMigrationJob にはscope_to ヘルパーメソッドが用意されており、 追加のフィルタを適用してこれを実現します:

  1. BatchedMigrationJob を継承し、追加フィルターを定義する新しいマイグレーションジョブクラスを作成します:

    class BackfillNamespaceType < BatchedMigrationJob
      scope_to ->(relation) { relation.where(type: nil) }
      operation_name :update_all
      feature_category :source_code_management
       
      def perform
        each_sub_batch do |sub_batch|
          sub_batch.update_all(type: 'User')
        end
      end
    end
    
    note
    scope_to を定義する EE マイグレーションでは、モジュールがActiveSupport::Concern を拡張していることを確認してください。そうしないと、スコープを考慮せずにレコードが処理されます。
  2. デプロイ後のマイグレーションでは、バッチ化されたバックグラウンドマイグレーションをエンキューします:

    class BackfillNamespaceType < Gitlab::Database::Migration[2.1]
      MIGRATION = 'BackfillNamespaceType'
      DELAY_INTERVAL = 2.minutes
       
      restrict_gitlab_migration gitlab_schema: :gitlab_main
       
      def up
        queue_batched_background_migration(
          MIGRATION,
          :namespaces,
          :id,
          job_interval: DELAY_INTERVAL
        )
      end
       
      def down
        delete_batched_background_migration(MIGRATION, :namespaces, :id, [])
      end
    end
    
note
追加のフィルタを適用する場合、EachBatch のパフォーマンスを最適化するために、それらがインデックスによって適切にカバーされていることを確認することが重要です。上の例では、フィルタをサポートするために(type, id) にインデックスが必要です。詳細はEachBatch のドキュメントを参照してください。

複数のデータベースのデータにアクセス

バックグラウンドマイグレーションは、通常のマイグレーションとは異なり、複数のデータベースにアクセスすることができ、複数のデータベースにまたがって効率的にデータにアクセスし、更新することができます。使用するデータベースを適切に示すために、マイグレーションコードのインラインでActiveRecordモデルを作成することが望まれます。このようなモデルは、テーブルがどのデータベースにあるかに応じて、正しいApplicationRecord 。このように、ActiveRecord::Base の使用は、指定されたテーブルにアクセスするために使用される明示的なデータベースを記述しないため、許可されません。

# good
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
  class Project < ::ApplicationRecord
    self.table_name = 'projects'
  end

  class Build < ::Ci::ApplicationRecord
    self.table_name = 'ci_builds'
  end
end

# bad
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
  class Project < ActiveRecord::Base
    self.table_name = 'projects'
  end

  class Build < ActiveRecord::Base
    self.table_name = 'ci_builds'
  end
end

同様に、ActiveRecord::Base.connection の使用も認められませんので、できればモデル接続の使用と置き換える必要があります。

# good
Project.connection.execute("SELECT * FROM projects")

# acceptable
ApplicationRecord.connection.execute("SELECT * FROM projects")

# bad
ActiveRecord::Base.connection.execute("SELECT * FROM projects")

バックグラウンドマイグレーションを再キューイング

バッチ化されたバックグラウンドマイグレーションは、いくつかの理由で再実行する必要があるかもしれません:

  • マイグレーションにバグが含まれている(例)。
  • マイグレーションでデータをクリーンアップしましたが、アプリケーションロジックのバイパスにより、データが再び非正規化されました例)。
  • 元のマイグレーションのバッチサイズが原因でマイグレーションが失敗しました例)。

バッチ化されたバックグラウンドマイグレーションを再実行するには、次の手順が必要です:

  • 元のマイグレーションファイルの#up メソッドと#down メソッドの内部をノーオープンにしてください。そうしないと、複数のパッチリリースを一度にアップグレードするシステムで、一括バックグラウンドマイグレーションが作成され、削除され、再び作成されます。
  • バッチバックグラウンドマイグレーションを再実行する新しいポストデプロイマイグレーションを追加します。
  • 新しいデプロイ後マイグレーションでは、#up メソッドの開始時にdelete_batched_background_migration メソッドを使用して既存のバッチバックグラウンドマイグレーションを削除し、既存の実行がクリーンアップされるようにします。
  • 元のマイグレーションからdb/docs/batched_background_migration/*.yml ファイルを更新して、リクエキューに関する情報を含めます。

非分離カラムに対するバッチ処理

デフォルトのバッチ処理方式は、主キーカラムを効率的に繰り返し処理するためのものです。しかし、値が一意でないカラムを繰り返し処理する必要がある場合は、 別のバッチ処理を使用しなければなりません。

LooseIndexScanBatchingStrategy バッチング戦略では、EachBatch の特別なバージョンを使用して、一意でないカラム値に対する効率的で安定した反復処理を行います。

この例では、issues.project_id 列がバッチ列として使用されるバッチバックグランドマイグレーションを示しています。

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

class ProjectsWithIssuesMigration < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BatchProjectsWithIssues'
  INTERVAL = 2.minutes
  BATCH_SIZE = 5000
  SUB_BATCH_SIZE = 500
  restrict_gitlab_migration gitlab_schema: :gitlab_main

  disable_ddl_transaction!
  def up
    queue_batched_background_migration(
      MIGRATION,
      :issues,
      :project_id,
      job_interval: INTERVAL,
      batch_size: BATCH_SIZE,
      batch_class_name: 'LooseIndexScanBatchingStrategy', # Override the default batching strategy
      sub_batch_size: SUB_BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(MIGRATION, :issues, :project_id, [])
  end
end

バックグラウンドマイグレーションクラスの実装:

module Gitlab
  module BackgroundMigration
    class BatchProjectsWithIssues < Gitlab::BackgroundMigration::BatchedMigrationJob
      include Gitlab::Database::DynamicModelHelpers

      operation_name :backfill_issues

      def perform
        distinct_each_batch do |batch|
          project_ids = batch.pluck(batch_column)
          # do something with the distinct project_ids
        end
      end
    end
  end
end
note
scope_to で定義された追加フィルターは、LooseIndexScanBatchingStrategydistinct_each_batch では無視されます。

バッチバックグランドマイグレーションの全体的な時間推定の計算

BBMが完了するまでの時間を見積もることができます。GitLab はすでにパイプラインを通して見積もりを提供していますdb:gitlabcom-database-testing 。この見積もりはテスト環境での本番データのサンプリングに基づいて構築されており、マイグレーションにかかる可能性のある最大時間を表しています。シナリオによっては db:gitlabcom-database-testing、パイプラインがdb:gitlabcom-database-testing 提供する見積もりだけ db:gitlabcom-database-testingでは、マイグレーションされるレコード周辺のすべての特異点を計算できず、さらなる計算が必要になることがあります。このような場合、interval * number of records / max batch size の式を使用して、マイグレーションにかかる時間の概算を求めることができます。ここで、intervalmax batch size はジョブに対して定義されたオプションを指し、total tuple count はマイグレーションされるレコードの数です。

note
見積もりはマイグレーション最適化メカニズムによって影響を受けるかもしれません。

バッチバックグランドマイグレーションのクリーンアップ

note
残っているバックグラウンドマイグレーションをクリーンアップするには、メジャーリリースかマイナーリリースで行う必要があります。パッチリリースでは行ってはいけません。

バックグラウンドマイグレーションには長い時間がかかることがあるため、キューに入れた後すぐに片付けることはできません。例えば、マイグレーションプロセスで使用されたカラムを削除することはできません。将来のリリースで_デプロイ後の_マイグレーションを別途追加し、残りのジョブを完了させてから片付ける必要があります。(例えば、カラムを削除するなど)。

カラムfoo (大きなJSONブロブを含む)からカラムbar (文字列を含む)にデータをマイグレーションするには、次のようにします:

  1. リリースA
    1. 指定された ID を持つ行のマイグレーションを実行するマイグレーション・クラスを作成します。
    2. これらの手法のいずれかを使用して、新しい行を更新します:
      • アプリケーションロジックを必要としないコピーオペレーション用の新しいトリガを作成します。
      • このオペレーションは、レコードが作成または更新されるときにモデル/サービスで処理します。
      • レコードを更新する新しいカスタムバックグラウンドジョブを作成します。
    3. デプロイ後のマイグレーションで、既存のすべての行のバッチバックグラウンドマイグレーションをキューに入れます。
  2. リリースB
    1. バッチバックグラウンドマイグレーションが完了したかどうかをチェックするデプロイ後のマイグレーションを追加しました。
    2. アプリケーションが新しいカラムの使用を開始し、新しいレコードの更新を停止するようにコードをデプロイします。
    3. 古いカラムを削除します。

以前のバージョンのGitLabからプロジェクトをインポートする際に、データを新しいフォーマットにする必要がある場合は、インポート/エクスポートのバージョンを上げる必要があるかもしれません。

バッチバックグランドマイグレーションをサポートするインデックスの追加

バッチ化されたバックグラウンドマイグレーションをサポートするために、新しいインデックスや一時的なインデックスを追加する必要がある場合があります。これを行うには、バックグラウンドマイグレーションをキューに入れるデプロイ後のマイグレーションより前に、デプロイ後のマイグレーションでインデックスを作成します。

作成後にインデックスを直接使用できるようにするために特別な注意を必要とするいくつかのケースに関する追加情報については、データベースインデックスの追加に関するドキュメントを参照してください。

データベースのテストパイプラインで特定のバッチを実行します。

note
データベースメンテナーのみが、データベーステストパイプラインのアーティファクトを見ることができます。この方法を使用する必要がある場合は、管理者に問い合わせてください。

GitLab.com の特定のバッチでバックグラウンドマイグレーションが失敗し、どのクエリがなぜ失敗したのかを調べたいとします。今のところ、クエリ情報 (特にクエリパラメータ) を取得する良い方法はありません。また、マイグレーション全体を再実行し、さらにロギングを行うには長い時間がかかります。

幸いなことに、私たちのデータベースマイグレーションパイプラインを活用することで、特定のバッチをログを追加したり修正したりして再実行し、問題が解決するかどうかを確認することができます。

例として、Draft:Test PG::CardinalityViolation fix を参照してください。

そのためには、以下のことが必要です:

  1. バッチstart_id を見つけend_id
  2. 通常のマイグレーションの作成
  3. マイグレーションヘルパーの回避策を適用(オプション)
  4. データベースマイグレーションパイプラインの開始

バッチstart_id を見つけend_id

Kibana でこれらを見つけることができるはずです。

通常のマイグレーションの作成

定期マイグレーションのup ブロックでバッチをスケジュールします:

def up
  instance = Gitlab::BackgroundMigration::YourBackgroundMigrationClass.new(
      start_id: <batch start_id>,
      end_id: <batch end_id>,
      batch_table: <table name>,
      batch_column: <batching column>,
      sub_batch_size: <sub batch size>,
      pause_ms: <miliseconds between batches>,
      job_arguments: <job arguments if any>,
      connection: connection
    )

    instance.perform
end

def down
  # no-op
end

マイグレーションヘルパーの回避策を適用する(オプション)

バッチ化されたバックグラウンドマイグレーションで、restrict_gitlab_migration ヘルパーを使用して指定したスキーマ以外のスキーマのテーブルに触れた場合 (例: スケジューリングマイグレーションではrestrict_gitlab_migration gitlab_schema: :gitlab_main を使用しているが、バックグラウンドジョブでは:gitlab_ci スキーマのテーブルを使用している)、マイグレーションは失敗します。これを防ぐには、テストパイプラインジョブを失敗させないようにデータベースヘルパーを修正する必要があります:

  1. スキーマ名をRestrictGitlabSchema
diff --git a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
index b8d1d21a0d2d2a23d9e8c8a0a17db98ed1ed40b7..912e20659a6919f771045178c66828563cb5a4a1 100644
--- a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
+++ b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
@@ -55,7 +55,7 @@ def unmatched_schemas
         end

         def allowed_schemas_for_connection
-          Gitlab::Database.gitlab_schemas_for_connection(connection)
+          Gitlab::Database.gitlab_schemas_for_connection(connection) << :gitlab_ci
         end
       end
     end
  1. スキーマ名をRestrictAllowedSchemas
diff --git a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
index 4ae3622479f0800c0553959e132143ec9051898e..d556ec7f55adae9d46a56665ce02de782cb09f2d 100644
--- a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
+++ b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
@@ -79,7 +79,7 @@ def restrict_to_dml_only(parsed)
             tables = self.dml_tables(parsed)
             schemas = self.dml_schemas(tables)

-            if (schemas - self.allowed_gitlab_schemas).any?
+            if (schemas - (self.allowed_gitlab_schemas << :gitlab_ci)).any?
               raise DMLAccessDeniedError, \
                 "Select/DML queries (SELECT/UPDATE/DELETE) do access '#{tables}' (#{schemas.to_a}) " \
                 "which is outside of list of allowed schemas: '#{self.allowed_gitlab_schemas}'. " \

データベースマイグレーションパイプラインの開始

変更内容でマージリクエストを作成し、手動db:gitlabcom-database-testing ジョブを起動します。

管理

note
BBMの管理はchatops インテグレーションを通して行われ、GitLabチームメンバーのみに限定されています。

バッチバックグランドマイグレーション一覧

システム内のバッチ化されたバックグラウンドマイグレーションを一覧表示するには、このコマンドを実行します:

/chatops run batched_background_migrations list

このコマンドは以下のオプションをサポートしています:

  • データベースの選択:
    • --database DATABASE_NAME:指定されたデータベースに接続します:
      • main:メインデータベースを使用します(デフォルト)。
      • ci:CI データベースを使用します。
  • 環境選択:
    • --dev:dev 環境を使用します。
    • --staging:staging 環境を使用します。
    • --staging_ref:staging_ref 環境を使用します。
    • --production production 環境を使用します (デフォルト)。

出力例:

List command

note
ChatOpsは、created_at (DESC)の順番で20のバッチバックグランドマイグレーションを返します。

バッチバックグラウンドマイグレーションの進行状況とステータスの監視

特定のバッチバックグラウンドマイグレーションのステータスと進捗状況を確認するには、このコマンドを実行します:

/chatops run batched_background_migrations status MIGRATION_ID

このコマンドは以下のオプションをサポートしています:

  • データベースの選択:
    • --database DATABASE_NAME:指定されたデータベースに接続します:
      • main:メインデータベースを使用 (デフォルト)
      • ci:CIデータベースを使用
  • 環境選択:
    • --dev:dev 環境を使用します。
    • --staging:staging 環境を使用します。
    • --staging_ref:staging_ref 環境を使用します。
    • --production production 環境を使用します (デフォルト)。

出力例:

Status command

Progress はバックグラウンドマイグレーションが完了した割合を表します。

バッチバックグラウンドマイグレーション状態の定義:

  • アクティビティ:どちらか:
    • ランナーに選ばれる準備ができています。
    • バッチジョブの実行。
  • ファイナライズバッチジョブの実行。
  • 失敗しました:バッチバックグラウンドマイグレーションに失敗しました。
  • 終了しました:バッチバックグラウンドマイグレーションが完了しました。
  • 一時停止:ランナーには表示されません。

バッチバックグラウンドマイグレーションを一時停止します。

バッチバックグラウンドマイグレーションを一時停止したい場合は、以下のコマンドを実行する必要があります:

/chatops run batched_background_migrations pause MIGRATION_ID

このコマンドは以下のオプションをサポートしています:

  • データベースの選択:
    • --database DATABASE_NAME:指定されたデータベースに接続します:
      • main:メインデータベースを使用します(デフォルト)。
      • ci:CI データベースを使用します。
  • 環境選択:
    • --dev:dev 環境を使用します。
    • --staging:staging 環境を使用します。
    • --staging_ref:staging_ref 環境を使用します。
    • --production production 環境を使用します (デフォルト)。

出力例:

Pause command

note
active バッチバックグラウンドマイグレーションのみを一時停止できます。

バッチバックグランドマイグレーション再開

バッチバックグラウンドマイグレーションを再開したい場合は、以下のコマンドを実行する必要があります:

/chatops run batched_background_migrations resume MIGRATION_ID

このコマンドは以下のオプションをサポートしています:

  • データベースの選択:
    • --database DATABASE_NAME:指定されたデータベースに接続します:
      • main:メインデータベースを使用します(デフォルト)。
      • ci:CI データベースを使用します。
  • 環境選択:
    • --dev:dev 環境を使用します。
    • --staging:staging 環境を使用します。
    • --staging_ref:staging_ref 環境を使用します。
    • --production production 環境を使用します (デフォルト)。

出力例:

Resume command

note
active バッチバックグラウンドマイグレーションのみ再開できます。

バックグラウンドマイグレーションを有効または無効にします。

極めて限定された状況において、GitLab管理者はこれらの機能フラグのどちらか、あるいは両方を無効にすることができます:

  • execute_background_migrations
  • execute_batched_migrations_on_schedule

これらのフラグはデフォルトで有効になっています。これらのフラグを無効にするのは、データベースホストのメンテナンスのような特別な状況でデータベースのオペレーションを制限するための最後の手段としてだけにしてください。

caution
これらのフラグを無効にすることで、どのような影響があるのかを十分に理解しない限り、これらのフラグを無効にしないでください。execute_background_migrations またはexecute_batched_migrations_on_schedule 機能フラグを無効にすると、GitLabのアップグレードが失敗し、データの損失が発生する可能性があります。

EE専用機能のバックグラウンド一括マイグレーション

EE専用機能のためのバックグラウンドマイグレーションクラスは全てGitLab FOSSに存在する必要があります。この目的のために、GitLab FOSS用に空のクラスを作成し、Enterprise Editionの機能を実装するためのガイドラインで説明されているように、GitLab EE用に拡張します。

note
ジョブ引数を使用するEE専用機能のバックグラウンドマイグレーションクラスは、GitLab FOSSクラスで定義する必要があります。GitLab FOSSコンテキストでマイグレーションがスケジュールされたときにジョブ引数のバリデーションが失敗しないようにするために定義が必要です。

新しいバッチドバックグラウンドマイグレーションを生成するときに--ee-only フラグを渡すことで、ジェネレータを使ってEE-only のマイグレーション雛形を生成することができます。

デバッグ

エラーログの表示

失敗を表示するには 2 つの方法があります:

  • GitLab ログから:
    1. バッチ化されたバックグラウンドマイグレーションを実行した後、いずれかのジョブが失敗した場合、Kibanaでログを表示します。本番Sidekiqログを表示し、フィルタリングします:

      • json.new_state: failed
      • json.job_class_name: <Batched Background Migration job class name>
      • json.job_arguments: <Batched Background Migration job class arguments>
    2. json.exception_classjson.exception_message の値をレビューして、ジョブが失敗した理由を理解します。

    3. リトライの仕組みを覚えておいてください。失敗したからといって、ジョブが失敗したわけではありません。常にジョブの最後のステータスをチェックしてください。

  • データベース経由で:

    1. バッチバックグランドマイグレーションを取得CLASS_NAME.
    2. PostgreSQLコンソールで以下のクエリを実行します:

       SELECT migration.id, migration.job_class_name, transition_logs.exception_class, transition_logs.exception_message
       FROM batched_background_migrations as migration
       INNER JOIN batched_background_migration_jobs as jobs
       ON jobs.batched_background_migration_id = migration.id
       INNER JOIN batched_background_migration_job_transition_logs as transition_logs
       ON transition_logs.batched_background_migration_job_id = jobs.id
       WHERE transition_logs.next_status = '2' AND migration.job_class_name = "CLASS_NAME";
      

テスト

テストの作成は、次のような場合に必要です:

  • バッチバックグランドマイグレーションのキューイングマイグレーション。
  • バッチバックグランドマイグレーションそのもの。
  • クリーンアップマイグレーション。

:migration およびschema: :latest RSpec タグはバックグラウンドマイグレーション仕様に自動的に設定されます。Testing Railsマイグレーションスタイルガイドを参照してください。

before およびafter RSpec フックがデータベースをダウンマイグレーションおよびアップマイグレーションすることを覚えておいてください。これらのフックを使用すると、他のバッチバックグラウンドマイグレーションが呼び出される可能性があります。spy have_received it ブロックで定義した期待値が、RSpec フックで呼び出されるものと衝突する可能性があるからです。詳細はイシュー#35351を参照してください。

ベストプラクティス

  1. 扱うデータの量を把握しましょう。
  2. バッチ化されたバックグラウンドマイグレーションジョブが冪等であることを確認してください。
  3. あなたが書いたテストが偽陽性でないことを確認してください。
  4. マイグレーションされるデータがクリティカルで失うことができない場合、クリーンアップマイグレーションは完了する前にデータの最終状態もチェックする必要があります。
  5. データベースのスペシャリストと数字について話し合ってください。マイグレーションは、予想以上にDBに負担をかける可能性があります。ステージングで測定するか、本番環境で測定するよう誰かに依頼してください。
  6. バッチバックグランドマイグレーションを実行するのに必要な時間を把握してください。
  7. ジョブクラスの内部で例外をサイレントレスキューする場合は注意してください。これは、失敗シナリオであっても、ジョブが成功としてマークされる可能性があります。

    # good
    def perform
      each_sub_batch do |sub_batch|
        sub_batch.update_all(name: 'My Name')
      end
    end
       
    # acceptable
    def perform
      each_sub_batch do |sub_batch|
        sub_batch.update_all(name: 'My Name')
      rescue Exception => error
        logger.error(message: error.message, class: error.class)
       
        raise
      end
    end
       
    # bad
    def perform
      each_sub_batch do |sub_batch|
        sub_batch.update_all(name: 'My Name')
      rescue Exception => error
        logger.error(message: error.message, class: self.class.name)
      end
    end
    

使用例

ルートの使用例

routes テーブルにはsource_type フィールドがあり、多相リレーションシップに使用されています。データベースの再設計の一環として、多相リレーションシップを削除します。その作業の一段階として、source_id 列のデータを新しい単数の外部キーにマイグレーションします。古い行は後で削除する予定なので、バックグラウンドマイグレーションの一部として更新する必要はありません。

  1. ジェネレータを使用して、バックグラウンドマイグレーションファイルをバッチで作成することから始めます:

    bundle exec rails g batched_background_migration BackfillRouteNamespaceId --table_name=routes --column_name=id --feature_category=source_code_management
    
  2. マイグレーションジョブ(BatchedMigrationJob のサブクラス)を更新して、source_id の値をnamespace_id にコピーします:

    class Gitlab::BackgroundMigration::BackfillRouteNamespaceId < BatchedMigrationJob
      # For illustration purposes, if we were to use a local model we could
      # define it like below, using an `ApplicationRecord` as the base class
      # class Route < ::ApplicationRecord
      #   self.table_name = 'routes'
      # end
       
      operation_name :update_all
      feature_category :source_code_management
       
      def perform
        each_sub_batch(
          batching_scope: -> (relation) { relation.where("source_type <> 'UnusedType'") }
        ) do |sub_batch|
          sub_batch.update_all('namespace_id = source_id')
        end
      end
    end
    
    note
    ジョブクラスはBatchedMigrationJob バッチマイグレーションフレームワークによって正しく処理さ BatchedMigrationJobれるように継承します。BatchedMigrationJob のサブクラスは BatchedMigrationJobバッチを実行するために必要な引数とトラッキングデータベースへの接続で初期化されます。
  3. データベースに新しいトリガを追加するデータベースマイグレーションを作成します。例

    class AddTriggerToRoutesToCopySourceIdToNamespaceId < Gitlab::Database::Migration[2.1]
      FUNCTION_NAME = 'example_function'
      TRIGGER_NAME = 'example_trigger'
       
      def up
        execute(<<~SQL)
          CREATE OR REPLACE FUNCTION #{FUNCTION_NAME}() RETURNS trigger
          LANGUAGE plpgsql
          AS $$
          BEGIN
            NEW."namespace_id" = NEW."source_id"
            RETURN NEW;
          END;
          $$;
       
          CREATE TRIGGER #{TRIGGER_NAME}() AFTER INSERT OR UPDATE
          ON routes
          FOR EACH ROW EXECUTE FUNCTION #{FUNCTION_NAME}();
        SQL
      end
       
      def down
        drop_trigger(TRIGGER_NAME, :routes)
        drop_function(FUNCTION_NAME)
      end
    end
    
  4. 作成したデプロイ後のマイグレーションを、必要な遅延とバッチサイズで更新します:

    class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[2.1]
      MIGRATION = 'BackfillRouteNamespaceId'
      DELAY_INTERVAL = 2.minutes
      BATCH_SIZE = 1000
      SUB_BATCH_SIZE = 100
       
      restrict_gitlab_migration gitlab_schema: :gitlab_main
       
      def up
        queue_batched_background_migration(
          MIGRATION,
          :routes,
          :id,
          job_interval: DELAY_INTERVAL,
          batch_size: BATCH_SIZE,
          sub_batch_size: SUB_BATCH_SIZE
        )
      end
       
      def down
        delete_batched_background_migration(MIGRATION, :routes, :id, [])
      end
    end
    
    note
    バッチバックグラウンドマイグレーションをキューイングする場合、スキーマを実際に変更を行うデータベースに制限する必要があります。この場合、routes レコードを更新するので、restrict_gitlab_migration gitlab_schema: :gitlab_main を設定します。しかし、CIデータマイグレーションを実行する必要がある場合は、restrict_gitlab_migration gitlab_schema: :gitlab_ci を設定します。

    デプロイ後、私たちのアプリケーション: - 以前と同様にデータを使用します。 - 既存データと新規データの両方を確実にマイグレーション。

  5. バッチ化されたバックグラウンドマイグレーションが完了したことを確認する新しいデプロイ後マイグレーションを追加します。例えば

    class FinalizeBackfillRouteNamespaceId < Gitlab::Database::Migration[2.1]
      MIGRATION = 'BackfillRouteNamespaceId'
      disable_ddl_transaction!
       
      restrict_gitlab_migration gitlab_schema: :gitlab_main
       
      def up
        ensure_batched_background_migration_is_finished(
          job_class_name: MIGRATION,
          table_name: :routes,
          column_name: :id,
          job_arguments: [],
          finalize: true
        )
      end
       
      def down
        # no-op
      end
    end
    
    note
    バッチ化されたバックグラウンドマイグレーションが終了していない場合、システムはバッチ化されたバックグラウンドマイグレーションをインラインで実行します。この動作を見たくない場合は、finalize: false.

    アプリケーションが100%マイグレーションされるデータに依存していない場合(例えば、データはアドバイザリーであり、ミッションクリティカルではない)、この最終ステップはスキップできます。このステップでは、マイグレーションが完了し、すべての行がマイグレーションされたことを確認します。

  6. データベースマイグレーションを追加して、トリガーを削除します。

    class RemoveNamepaceIdTriggerFromRoutes < Gitlab::Database::Migration[2.1]
      FUNCTION_NAME = 'example_function'
      TRIGGER_NAME = 'example_trigger'
       
      def up
        drop_trigger(TRIGGER_NAME, :routes)
        drop_function(FUNCTION_NAME)
      end
       
      def down
        # Should reverse the trigger and the function in the up method of the migration that added it
      end
    end
    

バッチマイグレーションが完了した後、routes.namespace_id のデータに安心して依存することができます。