- オペレーション
- 検証
- 例外
- 複数データベースのマイグレーションに関する今後の変更点
- バックグラウンドマイグレーション
- 指定したテーブルの
gitlab_schema
を決定する方法
複数データベースのマイグレーション
マイグレーション目的の記述のサポートがGitLab 14.8で導入されました。
このドキュメントでは、複数のデータベースを使用して分解されたGitLabアプリケーションのためのデータベースマイグレーションを適切に記述する方法を説明します。詳細については、複数のデータベースを参照してください。
複数のデータベース(Geoデータベースを除く)の設計では、分解されたデータベースはすべて同じ構造(例えばスキーマ)を持っていますが、データはそれぞれのデータベースで異なっていることを想定しています。つまり、一部のテーブルには各データベース上のデータが含まれません。
オペレーション
使用される構成によって、マイグレーションは次のいずれかに分類されます:
- 構造の変更(DDL - Data Definition Language)(
ALTER TABLE
など)。 - データの変更(DML - Data Manipulation Language)(
UPDATE
など)。 - マイグレーションではDMLとして扱われるその他のクエリの実行 (例えば
SELECT
)。
** Gitlab::Database::Migration[2.0]
の使用では、マイグレーションは常に単一の目的である必要があります。マイグレーションでは **、DDL と DML を混在させることはできません 。 の変更は、(db/structure.sql
で記述されているように)すべての分解されたデータベースでまったく同じ構造である必要があるからです。
データ定義言語(DDL)
DDL マイグレーションは、以下のようなすべてのマイグレーションです:
- テーブルの作成または削除 (例えば、
create_table
)。 - インデックスを追加または削除します (例:
add_index
,add_concurrent_index
)。 - 外部キーを追加または削除します (例:
add_foreign_key
,add_concurrent_foreign_key
)。 - デフォルト値の有無にかかわらず、カラムを追加または削除します (例:
add_column
)。 - トリガー関数の作成と削除 (例:
create_trigger_function
)。 - テーブルからトリガをアタッチまたはデタッチします (例:
track_record_deletions
,untrack_record_deletions
)。 - 非同期インデックスを準備するかしないか (例:
prepare_async_index
,unprepare_async_index_by_name
)。
このようなDDLマイグレーションはできません:
- SQLステートメントやActiveRecordモデルを介して、いかなる形式でもデータを読み取ったり変更したりすることはできません。
- カラム値の更新 (例:
update_column_in_batches
)。 - バックグラウンドマイグレーションをスケジュール(例:
queue_background_migration_jobs_by_range_at_intervals
)。 - 機能フラグは
main:
(features
とfeature_gates
) に格納されているので、フラグの状態を読み取ります。 - アプリケーション設定の読み取り(設定は
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
例単一のデータベースに格納する新しいテーブルの追加
-
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
-
スキーマ・マイグレーションでテーブルを作成します:
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マイグレーションは、以下のようなマイグレーションです:
- SQL 文 (例えば、
SELECT * FROM projects WHERE id=1
) を使ってデータを読み込みます。 - ActiveRecordモデルを使ってデータを読み込む (例:
User < MigrationRecord
). - ActiveRecordモデルによるデータの作成、更新、削除(例:
User.create!(...)
)。 - SQL 文によるデータの作成、更新、削除 (
DELETE FROM projects WHERE id=1
など)。 - カラムの一括更新 (例:
update_column_in_batches(:projects, :archived, true)
)。 - バックグラウンドマイグレーションをスケジュール(例:
queue_background_migration_jobs_by_range_at_intervals
)。 - アプリケーション設定にアクセスします (例:
main:
データベース用に実行する場合はApplicationSetting.last
)。 -
main:
データベース用に実行する場合は、機能フラグの読み取りと変更。
DML マイグレーションはできません:
- すべての分解されたデータベースで
structure.sql
の一貫性を保つというルールが破られるためです。 - 別のデータベースからデータを読み込みます。
DML
マイグレーションタイプを示すには、マイグレーションクラスでrestrict_gitlab_migration gitlab_schema:
構文を使用しなければなりません。これにより、指定されたマイグレーションは DML としてマークされ、アクセスが制限されます。
例: 与えられたマイグレーションを含むデータベースのコンテキストでのみ DML を実行します。gitlab_schema
gitlab_main
スキーマを含むデータベースに対してのみ実行される、projects
のarchived
カラムを更新するマイグレーション例。
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.yml
でgitlab_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.yml
でgitlab_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
)、各マイグレーションはすべてのデータベース構成に対して実行されます:
- DDL マイグレーションはすべてのデータベースに対してすべての構造の変更を適用します。
- DML マイグレーションは、指定された
gitlab_schema:
を含むデータベースのコンテクストでのみ実行されます。 - 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_config
。gitlab: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
オペレーションの順序によってDDLとDMLが混在するマイグレーションは、前の例外のいずれかを発生させます。
複数データベースのマイグレーションに関する今後の変更点
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
を決定する方法
データベース辞書を参照してください。