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

マイグレーション目的の記述のサポートがGitLab 14.8で導入されました。

このドキュメントでは、複数のデータベースを使用して分解されたGitLabアプリケーションのためのデータベースマイグレーションを適切に記述する方法を説明します。詳細については、複数のデータベースを参照してください。

複数のデータベース(Geoデータベースを除く)の設計では、分解されたデータベースはすべて同じ構造(例えばスキーマ)を持っていますが、データはそれぞれのデータベースで異なっていることを想定しています。つまり、一部のテーブルには各データベース上のデータが含まれません。

オペレーション

使用される構成によって、マイグレーションは次のいずれかに分類されます:

  1. 構造の変更(DDL - Data Definition Language)( ALTER TABLE など)。
  2. データの変更(DML - Data Manipulation Language)(UPDATE など)。
  3. マイグレーションではDMLとして扱われるその他のクエリの実行 (例えばSELECT)。

** Gitlab::Database::Migration[2.0] の使用では、マイグレーションは常に単一の目的である必要があります。マイグレーションでは **、DDLDML を混在させることはできません の変更は、(db/structure.sqlで記述されているように)すべての分解されたデータベースでまったく同じ構造である必要があるからです。

データ定義言語(DDL)

DDL マイグレーションは、以下のようなすべてのマイグレーションです:

  1. テーブルの作成または削除 (例えば、create_table)。
  2. インデックスを追加または削除します (例:add_index,add_concurrent_index)。
  3. 外部キーを追加または削除します (例:add_foreign_key,add_concurrent_foreign_key)。
  4. デフォルト値の有無にかかわらず、カラムを追加または削除します (例:add_column)。
  5. トリガー関数の作成と削除 (例:create_trigger_function)。
  6. テーブルからトリガをアタッチまたはデタッチします (例:track_record_deletions,untrack_record_deletions)。
  7. 非同期インデックスを準備するかしないか (例:prepare_async_index,unprepare_async_index_by_name)。

このようなDDLマイグレーションはできません

  1. SQLステートメントやActiveRecordモデルを介して、いかなる形式でもデータを読み取ったり変更したりすることはできません。
  2. カラム値の更新 (例:update_column_in_batches)。
  3. バックグラウンドマイグレーションをスケジュール(例:queue_background_migration_jobs_by_range_at_intervals )。
  4. 機能フラグはmain: (featuresfeature_gates) に格納されているので、フラグの状態を読み取ります。
  5. アプリケーション設定の読み取り(設定はmain: に格納されているため)。

GitLab コードベースのマイグレーションの大半は DDL タイプなので、これもデフォルトのオペレーションモードです。

例: すべてのデータベースに対してDDLを実行します。

設定されたすべてのデータベースで実行される(DDL) 構造の変更として扱われる同時インデックスの追加マイグレーション例。

class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'

  def up
    add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
  end

  def down
    remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
  end
end

例単一のデータベースに格納する新しいテーブルの追加

  1. db/docs/データベース辞書にテーブルを追加します:

    table_name: ssh_signatures
    description: Description example
    introduced_by_url: Merge request link
    milestone: Milestone example
    feature_categories:
     - Feature category example
    classes:
     - Class example
    gitlab_schema: gitlab_main
    
  2. スキーマ・マイグレーションでテーブルを作成します:

    class CreateSshSignatures < Gitlab::Database::Migration[2.1]
      def change
        create_table :ssh_signatures do |t|
          t.timestamps_with_timezone null: false
          t.bigint :project_id, null: false, index: true
          t.bigint :key_id, null: false, index: true
          t.integer :verification_status, default: 0, null: false, limit: 2
          t.binary :commit_sha, null: false, index: { unique: true }
        end
      end
    end
    

データ操作言語(DML)

DMLマイグレーションは、以下のようなマイグレーションです:

  1. SQL 文 (例えば、SELECT * FROM projects WHERE id=1) を使ってデータを読み込みます。
  2. ActiveRecordモデルを使ってデータを読み込む (例:User < MigrationRecord).
  3. ActiveRecordモデルによるデータの作成、更新、削除(例:User.create!(...) )。
  4. SQL 文によるデータの作成、更新、削除 (DELETE FROM projects WHERE id=1 など)。
  5. カラムの一括更新 (例:update_column_in_batches(:projects, :archived, true))。
  6. バックグラウンドマイグレーションをスケジュール(例:queue_background_migration_jobs_by_range_at_intervals )。
  7. アプリケーション設定にアクセスします (例:main: データベース用に実行する場合はApplicationSetting.last )。
  8. main: データベース用に実行する場合は、機能フラグの読み取りと変更。

DML マイグレーションはできません

  1. すべての分解されたデータベースでstructure.sql の一貫性を保つというルールが破られるためです。
  2. 別のデータベースからデータを読み込みます

DML マイグレーションタイプを示すには、マイグレーションクラスでrestrict_gitlab_migration gitlab_schema: 構文を使用しなければなりません。これにより、指定されたマイグレーションは DML としてマークされ、アクセスが制限されます。

例: 与えられたマイグレーションを含むデータベースのコンテキストでのみ DML を実行します。gitlab_schema

gitlab_main スキーマを含むデータベースに対してのみ実行される、projectsarchived カラムを更新するマイグレーション例。

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main

  def up
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end

  def down
    # no-op
  end
end

例:ActiveRecord クラスの使用法

ActiveRecord クラスを使用してデータ操作を行うマイグレーションでは、MigrationRecord クラスを使用しなければなりません。このクラスは与えられたマイグレーションのコンテキストにおいて正しい接続を提供することが保証されています。

db:migrate が実行されると、ActiveRecord::Base.establish_connection :ci のアクティブな接続が切り替わりますので、MigrationRecord == ActiveRecord::Base の下に、ActiveRecord::Base を使用する混乱を避けるために、MigrationRecord が必要です。

これは、DMLマイグレーションが他のデータベースからデータを読み込むことを禁止していることを意味します。例えば、ci: のコンテキストでマイグレーションを実行し、main: から機能フラグを読み取る場合、他のデータベースへの確立された接続が存在しないためです。

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main

  class Project < MigrationRecord
  end

  def up
    Project.where(archived: false).each_batch of |batch|
      batch.update_all(archived: true)
    end
  end

  def down
  end
end

の特別な目的はgitlab_shared

で説明されているように gitlab_schemaの特別な目的は、gitlab_shared テーブルがすべてのデータベースのデータを含むことができるようにすることです。このことは、構造(DDL) を変更したり、データ(DML)を変更したりするために、このようなマイグレーションをすべてのデータベースで実行する必要があることを意味します。

gitlab_shared にアクセスするマイグレーションは を使う必要がないのでrestrict_gitlab_migration gitlab_schema:、制限のないマイグレーションはすべてのデータベースにわたって実行され、それぞれのデータベースのデータを変更することが restrict_gitlab_migration gitlab_schema:できます。restrict_gitlab_migration gitlab_schema:restrict_gitlab_migration gitlab_schema:指定された場合、DML マイグレーションは指定されたgitlab_schema を含むデータベースのコンテキストでのみ実行されます。

例: すべてのデータベースで DMLgitlab_shared マイグレーションを実行します。

lib/gitlab/database/gitlab_schemas.ymlgitlab_shared としてマークされているloose_foreign_keys_deleted_records テーブルを更新するマイグレーション例。

このマイグレーションは設定されているすべてのデータベースで実行されます。

class DeleteAllLooseForeignKeyRecords < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    execute("DELETE FROM loose_foreign_keys_deleted_records")
  end

  def down
    # no-op
  end
end

例: DMLgitlab_shared を指定されたコンテナを含むデータベースに対してのみ実行します。gitlab_schema

db/docs/loose_foreign_keys_deleted_records.ymlgitlab_shared としてマークされているloose_foreign_keys_deleted_records テーブルを更新するマイグレーション例。

このマイグレーションは制限を設定するため、スキーマをgitlab_ci 含むデータベースのコンテキストでのみ実行さ gitlab_ciれます。

class DeleteCiBuildsLooseForeignKeyRecords < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_ci

  def up
    execute("DELETE FROM loose_foreign_keys_deleted_records WHERE fully_qualified_table_name='ci_builds'")
  end

  def down
    # no-op
  end
end

マイグレーションをスキップする動作

スキップされるマイグレーションはDML の変更を実行するものだけです。DDLマイグレーションは常に無条件に実行されます。

実装されたソリューションでは、どの追加データベース設定(config/database.yml )が同じプライマリデータベースを共有しているかを示す方法としてdatabase_tasks: を使用します。database_tasks: false でマークされたデータベース構成は、それらのデータベース構成に対してdb:migrate の実行が免除されます。

データベース構成がデータベースを共有していない場合(すべてdatabase_tasks: true )、各マイグレーションはすべてのデータベース構成に対して実行されます:

  1. DDL マイグレーションはすべてのデータベースに対してすべての構造の変更を適用します。
  2. DML マイグレーションは、指定されたgitlab_schema: を含むデータベースのコンテクストでのみ実行されます。
  3. DML マイグレーションが実行する資格がない場合、そのマイグレーションはスキップされます。それでもschema_migrations では実行されたとマークされます。db:migrate の実行中、スキップされたマイグレーションはCurrent migration is skipped since it modifies 'gitlab_ci' which is outside of 'gitlab_main, gitlab_shared を出力します。

database_tasks: false が設定されている場合にマイグレーションが失われるのを防ぐために、専用の Rake タスクが使用されますgitlab:db:validate_configgitlab:db:validate_config は、各基盤データベース設定のデータベース識別子をチェックすることでdatabase_tasks: の正しさを検証します。データベースを共有するものは、database_tasks: false が設定されている必要があります。gitlab:db:validate_config は常にdb:migrateの前に実行されます。

検証

バリデーションを簡単に説明すると、pg_query を使用して各クエリを分析し、db/docs/ からの情報を使用してテーブルを分類します。指定されたgitlab_schema が、指定されたデータベース接続で管理されるスキーマのリスト (Gitlab::Database::gitlab_schemas_for_connection) から外れている場合、マイグレーションはスキップされます。

Gitlab::Database::Migration[2.0] には#migrate メソッドを拡張したGitlab::Database::MigrationHelpers::RestrictGitlabSchema が含まれています。マイグレーションの間、専用のクエリアナライザGitlab::Database::QueryAnalyzers::RestrictAllowedSchemas がインストールされ、restrict_gitlab_migration: で定義された許可スキーマのリストを受け付けます。実行されたクエリが許可されたスキーマの外にある場合、例外が発生します。

例外

誤用やrestrict_gitlab_migration の不足により、マイグレーション実行の一部として様々な例外が発生し、マイグレーションが完了しないことがあります。

例外1: DDLモードで実行されているマイグレーションがDMLセレクトを行います。

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # Missing:
  # restrict_gitlab_migration gitlab_schema: :gitlab_main

  def up
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end

  def down
    # no-op
  end
end
Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode
Modifying of 'projects' (gitlab_main) with 'SELECT * FROM projects...

現在のマイグレーションはrestrict_gitlab_migration を使用していません。この欠落はDDLモードで実行されているマイグレーションを示していますが、実行されたペイロードはprojects からデータを読み出しているようです。

解決策はrestrict_gitlab_migration gitlab_schema: :gitlab_main を追加することです。

例外2: DMLモードで実行されるマイグレーションが構造を変更する場合

class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # restrict_gitlab_migration if defined indicates DML, it should be removed
  restrict_gitlab_migration gitlab_schema: :gitlab_main

  INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'

  def up
    add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
  end

  def down
    remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
  end
end
DDL queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode.
Modifying of 'merge_request_reviewers' with 'CREATE INDEX...

現在のマイグレーションはrestrict_gitlab_migration を使用しています。存在はDMLモードを示しますが、実行されたペイロードは構造変更を行っているようです(DDL)。

解決策はrestrict_gitlab_migration gitlab_schema: :gitlab_main を削除することです。

例外3: DMLモードで実行されているマイグレーションが別のスキーマのテーブルからデータにアクセスする場合

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # Since it modifies `projects` it should use `gitlab_main`
  restrict_gitlab_migration gitlab_schema: :gitlab_ci

  def up
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end

  def down
    # no-op
  end
end
Select/DML queries (SELECT/UPDATE/DELETE) do access 'projects' (gitlab_main) " \
which is outside of list of allowed schemas: 'gitlab_ci'

現在のマイグレーションはgitlab_ci にマイグレーションを制限していますが、gitlab_main のデータを変更しているように見えます。

解決策はrestrict_gitlab_migration gitlab_schema: :gitlab_ci を変更することです。

例外4: DDLモードとDMLモードの混在

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # This migration is invalid regardless of specification
  # as it cannot modify structure and data at the same time
  restrict_gitlab_migration gitlab_schema: :gitlab_ci

  def up
    add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: 'index_on_merge_request_reviewers'
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end

  def down
    # no-op
  end
end

オペレーションの順序によってDDLDMLが混在するマイグレーションは、前の例外のいずれかを発生させます。

複数データベースのマイグレーションに関する今後の変更点

gitlab_schema: を使用したrestrict_gitlab_migration は、コンテキストに応じて選択的にマイグレーションを実行するための、この機能の最初のイテレーションと考えられます。DML だけのマイグレーションに、実行するタイミングを制限するための追加の制限を追加することは可能です (構造の一貫性は、さらなる予告があるまで、このままであると思われます)。

可能性のある拡張は、DMLマイグレーションの実行を特定の環境のみに制限することです:

restrict_gitlab_migration gitlab_schema: :gitlab_main, gitlab_env: :gitlab_com

バックグラウンドマイグレーション

を使用する場合:

  • true 、またはtrack_jobs を設定したバックグラウンドマイグレーション。
  • バックグラウンドでの一括マイグレーション

マイグレーションはジョブテーブルに書き込む必要があります。バックグラウンドマイグレーションで使用されるすべてのジョブテーブルはgitlab_shared としてマークされます。どのデータベースのテーブルをマイグレーションする場合でも、これらのマイグレーションを使用することができます。

しかし、バッチをキューイングする際には、繰り返し処理するテーブルに基づいてrestrict_gitlab_migration を設定する必要があります。例えば、すべてのprojects を更新する場合、restrict_gitlab_migration gitlab_schema: :gitlab_mainを設定します。しかし、ci_pipelinesをすべて更新する場合は、restrict_gitlab_migration gitlab_schema: :gitlab_ciを設定します。

すべての DML マイグレーションと同様に、restrict_gitlab_migration またはgitlab_shared 以外の別のデータベースにクエリすることはできません。 別のデータベースにクエリする必要がある場合は、マイグレーションを分けてください。

バックグラウンドマイグレーションの実際のマイグレーションロジック(キューイングステップではない)はSidekiqワーカーで実行されるため、通常のSidekiqワーカーと同様に、ロジックはどのデータベースのテーブルに対してもDMLクエリを実行できます。

指定したテーブルのgitlab_schema を決定する方法

データベース辞書を参照してください。