- GitLab スキーマ
- マイグレーション
- CI/CD データベース
- データベースをまたがる外部キー
- 複数データベースのテスト
- main_clusterwideを含む複数データベースのテスト
- データベーススキーマに属さないテーブルへの書き込みのロック
- テーブルの切り捨て
複数のデータベース
GitLabをさらに拡張できるようにするために、私たちはGitLabアプリケーションデータベースを複数のデータベースに分割しました。二つのデータベースはmain
とci
です。GitLabは1つのデータベースでも2つのデータベースでも実行できます。GitLab.comでは、2つの別々のデータベースを使っています。
GitLab スキーマ
異なるデータベース間で許容されるパターンを適切に発見するために、GitLabアプリケーションはデータベース辞書を実装しています。
データベースディクショナリは、gitlab_schema
PostgreSQLスキーマに似た概念的 gitlab_schema
なテーブルの仮想的な分類を提供します。gitlab_schema
私たちは、CIを分解した機能をよりよく分離するためにデータベーススキーマを使用する一環として、複雑なマイグレーション手順のためにPostgreSQLスキーマを使用できないことを決定しました。その代わりに、私たちはアプリケーションレベルの分類という概念を実装しました。GitLabの各テーブルには gitlab_schema
割り当てがgitlab_schema
必要 gitlab_schema
です:
-
gitlab_main
例えば、projects
,users
のように)main:
データベースに格納されている全てのテーブルを記述します。 -
gitlab_ci
:ci:
データベースに格納されているすべての CI テーブルについて説明します (例:ci_pipelines
,ci_builds
)。 -
gitlab_geo
:geo:
データベースに格納されているすべての Geo テーブルについて説明します (例:project_registry
,secondary_usage_data
)。 -
gitlab_shared
Gitlab::Database::SharedModel
を継承するモデルについて、すべての分解されたデータベース(たとえば、loose_foreign_keys_deleted_records
)にまたがるデータを含むすべてのアプリケーションテーブルを記述します。 -
gitlab_internal
: RailsとPostgreSQLのすべての内部テーブルを記述します (たとえば、ar_internal_metadata
,schema_migrations
,pg_*
)。 -
gitlab_pm
:package_metadata
を格納するすべてのテーブルについて説明します (gitlab_main
のエイリアスです)。 -
...
分解されたデータベースを追加することで、より多くのスキーマが導入されます。
スキーマを使用すると、使用する基本クラスが強制されます:
-
ApplicationRecord
に対してgitlab_main
-
Ci::ApplicationRecord
に対してgitlab_ci
-
Geo::TrackingBase
に対してgitlab_geo
-
Gitlab::Database::SharedModel
に対してgitlab_shared
-
PackageMetadata::ApplicationRecord
に対してgitlab_pm
の影響gitlab_schema
gitlab_schema
の使用はアプリケーションに大きな影響を与えます。gitlab_schema
の主な目的は、異なるデータ・アクセス・パターン間にバリアを導入することです。
これは、分類のための主要なソースとして使用されます:
の特別な目的はgitlab_shared
gitlab_shared
は、設計上、すべての分解されたデータベースにまたがるデータを含むテーブルまたはビューを記述する特殊なケースです。この分類では、アプリケーション定義テーブル(loose_foreign_keys_deleted_records
など)について説明します。
gitlab_shared
データへのアクセス時に特別な処理が必要になるため gitlab_shared
、使用には注意が必要です。構造だけでなくデータも共有するgitlab_shared
ため gitlab_shared
、アプリケーションはすべてのデータベースのすべてのデータを順次走査するように記述する必要があります。
Gitlab::Database::EachDatabase.each_model_connection([MySharedModel]) do |connection, connection_name|
MySharedModel.select_all_data...
end
そのため、gitlab_shared
テーブルのデータを変更するマイグレーションは、分解されたすべてのデータベースで実行されることが予想されます。
の特別な目的はgitlab_internal
gitlab_internal
には、Railsで定義されたテーブル(schema_migrations
やar_internal_metadata
など)やPostgreSQLの内部テーブル(pg_attribute
など)が記述されています。その主な目的は、Geoのような他のデータベースをサポートすることです。そのようなデータベースには、アプリケーション定義のgitlab_shared
テーブル (loose_foreign_keys_deleted_records
のような) がない可能性がありますが、有効なRailsデータベースです。
の特別な目的はgitlab_pm
gitlab_pm
には、公開リポジトリを記述するパッケージメタデータが格納されます。このデータは、ライセンスコンプライアンスと依存関係スキャンの製品カテゴリで使用され、コンポジション分析グループによってメンテナーされています。gitlab_main
のエイリアスで、将来的に別のデータベースへのルーティングを容易にすることを目的としています。
マイグレーション
CI/CD データベース
単一データベースの設定
デフォルトでは、GDKは複数のデータベースで動作するように設定されています。
ci
データベース内のすべてのデータにアクセスできません。単一データベースの場合は、別の開発インスタンスを使用してください。単一データベースを使用するようにGDKを設定するには、以下の手順に従います:
-
GDKのルートディレクトリで
gdk config set gitlab.rails.databases.ci.enabled false
-
GDKを再構成します:
gdk reconfigure
複数のデータベースを使用するように戻すには、gitlab.rails.databases.ci.enabled
をtrue
に設定し、gdk reconfigure
を実行します。
テーブルci
間および ci
テーブルci
以外の ci
結合の削除
データベースをまたいで結合するクエリはエラーになります。GitLab 14.3 で導入された、新しいクエリのみ。既存のクエリはエラーになりません。
GitLabは複数の別々のデータベースで実行できるため、一つのクエリでci
テーブルと ci
テーブルci
以外を ci
参照することはできません。そのため、SQLクエリでJOIN
。
データベースをまたいだ結合を削除するための提案
以下のセクションは、データベースをまたいだ結合が確認された実際の例と、その修正方法に関する提案です。
コードの削除
これまで何度か見てきた最も単純な解決策は、使われていない既存のスコープです。これは最も簡単に解決できる例です。ですから、最初のステップは、そのコードが未使用かどうかを調査し、それを削除することです。これらは実際の例です:
- https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67162
- https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66714
- https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66503
このコードが使われている例はもっとあるかもしれませんが、そのコードが必要かどうか、あるいはその機能がこのように振る舞うべきかどうかを評価することができます。新しい列やテーブルを追加して物事を複雑にする前に、ソリューションを単純化しても要件を満たせるかどうかを検討してください。評価されるケースの1つは、UsageData
https://gitlab.com/gitlab-org/gitlab/-/issues/336170の結合クエリを削除するために UsageData
、ある特定のUsageData
計算 UsageData
方法を変更することです。UsageData
なぜなら UsageData
、ユーザーにとって重要なメトリクスではなく、より単純なアプローチで同様の有用なメトリクスを得ることができる可能性があるからです。また、誰もこれらのメトリクスを使用していないことがわかり、削除することもできます。
の代わりにpreload
を使用します。includes
Railsのincludes
メソッドとpreload
メソッドは、どちらもN+1クエリを回避する方法です。Railsのincludes
メソッドは、テーブルに結合する必要があるのか、別のクエリですべてのレコードをロードできるのかを判断するために発見的アプローチを使用します。このメソッドは、他のテーブルのカラムをクエリする必要があると判断した場合、ジョインする必要があると判断しますが、このメソッドが誤って、必要ない場合でもジョインを実行してしまうことがあります。この場合、preload
を使用して明示的に別のクエリでデータをロードすることで、結合を回避しつつN+1クエリを回避することができます。
このソリューションが実際に使用されている例をhttps://gitlab.com/gitlab-org/gitlab/-/merge_requests/67655で見ることができます。
冗長な結合の削除
クエリが過剰な(または冗長な)結合を行っている場合があります。
よくある例は、クエリがA
からC
へ、両方の外部キーを持つテーブルB
を経由して結合している場合です。C
、B
の外部キーにNOT NULL
制約がある場合、 に何行あるか数えることにしか関心がない場合、これらの行を数えるだけで十分かもしれません。例えば、MR 71811では、以前はproject.runners.count
のようなクエリを作成していました:
select count(*) from projects
inner join ci_runner_projects on ci_runner_projects.project_id = projects.id
where ci_runner_projects.runner_id IN (1, 2, 3)
これは、コードをproject.runner_projects.count
に変更することで、クロスジョインを回避するように変更されました。 これは以下のクエリと同じレスポンスを生成します:
select count(*) from ci_runner_projects
where ci_runner_projects.runner_id IN (1, 2, 3)
もう1つのよくある冗長な結合は、外部キーでフィルタリングする代わりに主キーでフィルタリングし、別のテーブルまで結合することです。MR 71614の例を参照してください。前のコードはjoins(scan: :build).where(ci_builds: { id: build_ids })
、次のようなクエリを生成しました:
select ...
inner join security_scans
inner join ci_builds on security_scans.build_id = ci_builds.id
where ci_builds.id IN (1, 2, 3)
しかし、security_scans
はすでに外部キーbuild_id
を持っているので、joins(:scan).where(security_scans: { build_id: build_ids })
にコードを変更することができます:
select ...
inner join security_scans
where security_scans.build_id IN (1, 2, 3)
冗長な結合を削除するこれらの例はどちらもクロス結合を削除しますが、よりシンプルで高速なクエリを作成できるという利点があります。
限定的な摘出とそれに続く検索
pluck
やpick
を使ってid
s の配列を取得するのは、 返される配列のサイズが有限であることが保証されていない限りお勧め id
できません。id
通常、これは結果が最大で 1 になることがわかっている場合や、メモリ上の ID (あるいはユーザ名) のリストを同じサイズの別のリストにマップする必要がある場合に有効なパターンです。一対多のリレーションで ID のリストをマップする場合は、結果が無制限になるため適していません。返された id
s を使って、関連レコードを取得することができます:
allowed_user_id = board_user_finder
.where(user_id: params['assignee_id'])
.pick(:user_id)
User.find_by(id: allowed_user_id)
これを使用した例をhttps://gitlab.com/gitlab-org/gitlab/-/merge_requests/126856
joinをpluck
、簡単に変換できるように思われるかもしれませんが、多くの場合、メモリに無限のidをロードし、次のクエリでそれらのidをPostgresに戻すために再シリアライズすることになります。このようなケースはスケールしないので、他の選択肢を試すことを勧めます。limit
、取り出したデータをメモリにバインドするのが良いように思われるかもしれませんが、これはユーザーにとって予測不可能な結果をもたらし、(私たちを含む)私たちの最大の顧客にとって最も問題となることがよくあります。
テーブルの外部キーの正規化を解除します。
非正規化とは、特定のクエリを単純化したり、パフォーマンスを向上させたりするために、冗長な事前計算(重複)データをテーブルに追加することを指します。この場合、3つのテーブルを含む結合を行う際に、中間テーブルを経由して結合する場合に有効です。
一般的にデータベーススキーマをモデリングする際には、以下の理由から “正規化された “構造が好まれます:
- 重複データは余分なストレージを使用します。
- 重複データは同期を保つ必要があります。
正規化されたデータはパフォーマンスが低下することがあるので、非正規化はGitLabがデータベースクエリのパフォーマンスを向上させるためにしばらくの間使ってきた一般的なテクニックです。上記の問題は、以下の条件を満たすことで緩和されます:
- データが少ない (たとえば整数カラムだけ)。
- データが頻繁に更新されない (例えば、
project_id
列はほとんどのテーブルでほとんど更新されません)。
1つの例として、security_scans
テーブルを見つけました。このテーブルには、ビルドに結合できる外部キーsecurity_scans.build_id
があります。そのため、次のようにプロジェクトに結合することができます:
select projects.* from security_scans
inner join ci_builds on security_scans.build_id = ci_builds.id
inner join projects on ci_builds.project_id = projects.id
このクエリの問題は、ci_builds
が他の2つのテーブルとは別のデータベースにあることです。
この場合の解決策は、security_scans
にproject_id
カラムを追加することです。これは余分なストレージを使用しませんし、このような機能の仕組み上、更新されることはありません(ビルドがプロジェクトを移動することはありません)。
これにより、クエリは次のように単純化されます:
select projects.* from security_scans
inner join projects on security_scans.project_id = projects.id
これは、余分なテーブルを経由して結合する必要がないため、パフォーマンスも向上します。
このアプローチはhttps://gitlab.com/gitlab-org/gitlab/-/merge_requests/66963 で実装されています。このMRはまた、pipeline_id
、同様のクエリを修正するために正規化を解除しています。
正規化を解除して追加テーブルを作成
前の非正規化(カラムの追加)が、特定のケースでうまくいかないことがあります。これは、データが1:1ではないことや、追加するテーブルがすでに広すぎることが原因かもしれません(たとえば、projects
テーブルにカラムを追加すべきではありません)。
この場合、余分なデータを別のテーブルに格納することになります。
この方法が使われている一例として、Project.with_code_coverage
スコープの実装があります。このスコープは、基本的に、ある時点でコードカバレッジ機能を使用したことがあるプロジェクトだけを絞り込むために使用しました。このクエリ(簡略化)は次のようなものでした:
select projects.* from projects
inner join ci_daily_build_group_report_results on ci_daily_build_group_report_results.project_id = projects.id
where ((data->'coverage') is not null)
and ci_daily_build_group_report_results.default_branch = true
group by projects.id
この作業はまだ進行中ですが、現在の計画では、project_id
とci_feature
の2つの列を持つprojects_with_ci_feature_usage
という新しいテーブルを導入する予定です。このテーブルは、プロジェクトがコード・カバレッジのためにci_daily_build_group_report_results
を最初に作成したときに書き込まれます。したがって、新しいクエリは次のようになります:
select projects.* from projects
inner join projects_with_ci_feature_usage on projects_with_ci_feature_usage.project_id = projects.id
where projects_with_ci_feature_usage.ci_feature = 'code_coverage'
上記の例では、簡単のためにテキスト・カラムを使用していますが、スペースを節約するために列挙型を使用すべきかもしれません。
この新しい設計の欠点は、これを更新する(ci_daily_build_group_report_results
が削除されたら削除する)必要があるかもしれないことです。しかし、あなたのドメインによっては、削除がエッジケースであったり、不可能であったり、一覧ページでプロジェクトを見ることによるユーザーへの影響が問題でなかったりするため、これは必要ないかもしれません。また、ドメイン内で必要な場合に、あるいは必要なときに、これらの行を削除するロジックを実装することも可能です。
最後に、この正規化解除と新しいクエリによって、結合の回数が減り、フィルタリングの回数が減るため、パフォーマンスも向上します。
has_one
またはhas_many
through:
のリレーションにはdisable_joins
を使用してください。
異なるデータベースにまたがるテーブル間でhas_one ... through:
またはhas_many
... through:
を使用することで、結合クエリが発生することがあります。このような結合は、disable_joins:true
を追加することで解決できることがあります。これはRailsの機能で、バックポートしました。また、機能フラグでdisable_joins
を有効にするラムダ構文が使えるように機能を拡張しました。この機能を使用する場合、機能フラグを使用することをお勧めします。
この機能を使用した例をhttps://gitlab.com/gitlab-org/gitlab/-/merge_requests/66709/diffs で見ることができます。
DBクエリの変更では、変更前と変更後のSQLを分析し比較することが重要です。disable_joins
、has_many
またはhas_one
リレーションシップの実際のロジックによっては、非常にパフォーマンスの悪いコードが導入される可能性があります。重要な点は、最終結果セットを構築するために使用される中間結果セットのいずれかに、制限のない量のデータがロードされているかどうかです。これを知る最善の方法は、生成されたSQLを見て、それぞれが何らかの方法で制限されていることを確認することです。LIMIT 1
句か、一意な列に基づいて制限しているWHERE
句でわかります。制限のない中間データセットがあると、メモリに多くのIDをロードしてしまう可能性があります。
パフォーマンスが非常に悪くなる例として、次のような仮想的なコードがあります:
class Project
has_many :pipelines
has_many :builds, through: :pipelines
end
class Pipeline
has_many :builds
end
class Build
belongs_to :pipeline
end
def some_action
@builds = Project.find(5).builds.order(created_at: :desc).limit(10)
end
上記の場合、some_action
のようなクエリが生成されます:
select * from builds
inner join pipelines on builds.pipeline_id = pipelines.id
where pipelines.project_id = 5
order by builds.created_at desc
limit 10
しかし、リレーションを次のように変更すると、次のようなクエリが生成されます:
class Project
has_many :pipelines
has_many :builds, through: :pipelines, disable_joins: true
end
とすると、次の2つのクエリが得られます:
select id from pipelines where project_id = 5;
select * from builds where pipeline_id in (...)
order by created_at desc
limit 10;
最初のクエリは一意なカラムによる制限やLIMIT
節を持たないため、無制限にパイプライン ID をメモリにロードすることができ、それが次のクエリで送信されます。これはRailsアプリケーションとデータベースのパフォーマンスを非常に低下させます。このような場合、クエリを書き直すか、クロスジョインを削除するために上で説明した他のパターンを検討する必要があるかもしれません。
正しくクロスジョインを削除したことを確認する方法
RSpec は、すべての SQL クエリがデータベース間で結合していないことを自動的に検証するように設定されています。この検証がspec/support/database/cross-join-allowlist.yml
で無効になっている場合でも、with_cross_joins_prevented
を使用して、孤立したコードブロックを検証することができます。
こ の方式は次の よ う に使用で き ます:
it 'does not join across databases' do
with_cross_joins_prevented do
::Ci::Build.joins(:project).to_a
end
end
クエリが2つのデータベースにまたがって結合すると、例外が発生します。前の例は、このように結合を削除することで修正できます:
it 'does not join across databases' do
with_cross_joins_prevented do
::Ci::Build.preload(:project).to_a
end
end
この方法でクロスジョインを修正した実際の例をhttps://gitlab.com/gitlab-org/gitlab/-/merge_requests/67655で見ることができます。
既存の交差結合の許可リスト
::Gitlab::Database.allow_cross_joins_across_databases
ヘルパーメソッドでコードをラップすることで、データベース間の交差結合を明示的に許可することができます。もう1つの方法は、指定されたリレーションをrelation.allow_cross_joins_across_databases
としてマークすることです。
このメソッドは
- 既存のコードの場合。
- クロスジョインからのマイグレーションに必要なコード。例えば、将来クロスジョインを取り除くためにデータを埋め戻すマイグレーション。
allow_cross_joins_across_databases
ヘルパーメソッドは以下のように使用できます:
# Scope the block executing a object from database
::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336590') do
subject.perform(1, 4)
end
# Mark a relation as allowed to cross-join databases
def find_actual_head_pipeline
all_pipelines
.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336891')
.for_sha_or_source_sha(diff_head_sha)
.first
end
url
パラメータは、クロスジョインを修正する予定のマイルストーンを持つイシューを指す必要があります。クロスジョインがマイグレーションで使用されている場合、コードを修正する必要はありません。詳細はhttps://gitlab.com/gitlab-org/gitlab/-/issues/340017 を参照してください。
クロスデータベーストランザクションの削除
複数のデータベースを扱う場合、複数のデータベースに影響するデータ変更に細心の注意を払うことが重要です。GitLab 14.4で導入された自動チェックは、データベースをまたいだ変更を防ぎます。
任意のデータベースサーバで開始されたトランザクション中に少なくとも2つの異なるデータベースが変更された場合、アプリケーションはクロスデータベース変更エラーをトリガーします(テスト環境のみ)。
使用例:
# Open transaction on Main DB
ApplicationRecord.transaction do
ci_build.update!(updated_at: Time.current) # UPDATE on CI DB
ci_build.project.update!(updated_at: Time.current) # UPDATE on Main DB
end
# raises error: Cross-database data modification of 'main, ci' were detected within
# a transaction modifying the 'ci_build, projects' tables
上記のコード例は、トランザクション内の2つのレコードのタイムスタンプを更新します。CIデータベースの分解作業が進行中であるため、データベーストランザクションの概略を保証することはできません。2つ目の更新クエリが失敗した場合、ci_build
レコードが別のデータベースサーバーにあるため、1つ目の更新クエリはロールバックされません。詳細については、トランザクションガイドラインのページをご覧ください。
クロスデータベースエラーの修正
トランザクション・ブロックの削除
オープン・トランザクションがなければ、データベース横断変更チェックはエラーを発生させることができません。この変更を行うことで、一貫性が犠牲になります。最初のクエリの後でアプリケーションに障害が発生した場合UPDATE
、2番目の UPDATE
クエリは決して実行されません。
transaction
ブロックのない同じコードです:
ci_build.update!(updated_at: Time.current) # CI DB
ci_build.project.update!(updated_at: Time.current) # Main DB
非同期処理
オペレーションが一貫して終了することをより保証する必要がある場合、バックグラウンドジョブ内で実行することができます。バックグラウンドジョブは非同期にスケジューリングされ、エラーが発生した場合に何度か再試行されます。それでも、不整合が発生する可能性はごくわずかです。
使用例:
current_time = Time.current
MyAsyncConsistencyJob.perform_async(cu_build.id)
ci_build.update!(updated_at: current_time)
ci_build.project.update!(updated_at: current_time)
MyAsyncConsistencyJob
、タイムスタンプが異なる場合は更新も試みます。
完全な一貫性を目指して
現時点では、ひとつのデータベースを使ったときと同じような一貫性特性を確保するためのツールはありません (必要ないかもしれません)。作業中のコードにこのような特性が必要だと思われる場合は、 問題のあるテストコードをブロックで囲み、 フォローアップ用のイシューを作成することで、 データベース間の変更チェックをテストで無効にすることができます。
allow_cross_database_modification_within_transaction(url: 'gitlab issue URL') do
ApplicationRecord.transaction do
ci_build.update!(updated_at: Time.current) # UPDATE on CI DB
ci_build.project.update!(updated_at: Time.current) # UPDATE on Main DB
end
end
ポッドグループに遠慮なく相談してください。
dependent: :nullify
、データベース間でdependent: :destroy
。
データベースをまたいでdependent: :nullify
またはdependent: :destroy
を使用したい場合があるかもしれません。これは技術的には可能ですが、#destroy
への呼び出しから内部トランザクションのコンテキストでこれらのフックが実行されるため、クロスデータベーストランザクションが発生し、これを回避しようとしているため、問題があります。なぜなら、トランザクションの外部でクエリが実行され、外部トランザクションが失敗している間に部分的にクエリが適用される可能性があるからです。
データベースの外部でデータをクリーンアップする必要がある非自明なオブジェクト(例えば、オブジェクトストレージ)については、dependent: :restrict_with_error
の設定を推奨します。このようなオブジェクトは、事前に明示的に削除しておく必要があります。dependent: :restrict_with_error
を使用することで、何かがクリーンアップされない場合に親オブジェクトを破壊することを確実に禁止します。
PostgreSQLから子レコード自体を削除するだけでよいのであれば、緩い外部キーの使用を検討してください。
データベースをまたがる外部キー
2つのデータベースをまたがって参照する外部キーを使用する場所がたくさんあります。これは、2つの別々のPostgreSQLデータベースでは不可能です。したがって、PostgreSQLから得られる動作を、実行可能な方法で複製する必要があります。PostgreSQLが提供する、無効な参照を作成しないデータ保証を複製することはできませんし、そうすべきでもありませんが、それでもカスケード削除を置き換える方法は必要です。そのため、私たちは“緩い外部キー “を作成しました。これは、孤児となったレコードを整理する非同期処理です。
複数データベースのテスト
私たちのテスト CI パイプラインでは、デフォルトで複数のデータベースを設定して GitLab をテストしています。main
とci
の両方のデータベースを使います。しかし、マージリクエストでは、たとえばデータベース関連のコードを修正したり MR に~"pipeline:run-single-db"
というラベルを追加したりするときに、single-db
とsingle-db-ci-connection
の二つのデータベースモードでテストを実行します。
特定のデータベースモードでテストを実行する必要がある場合に対応するため、RSpec ヘルパーをいくつか用意しました。
ヘルパー名 | テスト実行 |
---|---|
skip_if_shared_database(:ci) | 複数のデータベース |
skip_if_database_exists(:ci) | 単一DBおよび単一DB-CI接続の場合 |
skip_if_multiple_databases_are_setup(:ci) | シングルDBのみ |
skip_if_multiple_databases_not_setup(:ci) | 単一db-ci-接続と 複数データベースの場合 |
main_clusterwideを含む複数データベースのテスト
デフォルトでは、CIパイプラインでmain_clusterwide
接続を設定しません。しかし、~"pipeline:run-clusterwide-db"
というラベルを追加すると、パイプラインはmain
、ci
、main_clusterwide
の3つの接続で実行されます。
データベーススキーマに属さないテーブルへの書き込みのロック
CIデータベースが昇格させられ、2つのデータベースが完全に分割されたとき、スプリットブレインの状況を作らないための特別な安全策として、Rakeタスクgitlab:db:lock_writes
を実行してください。このコマンドは書き込みをロックします:
- CI データベースの
gitlab_main
テーブル。 - メインデータベースの
gitlab_ci
テーブル。
このRakeタスクはすべてのテーブルにトリガーを追加し、ロックが必要なテーブルに対してINSERT
、UPDATE
、DELETE
、TRUNCATE
ステートメントが実行されないようにします。
このタスクが、gitlab_main
とgitlab_ci
のテーブルに対して単一のデータベースのみを使用する GitLab セットアップに対して実行された場合、テーブルはロックされません。
オペレーションを元に戻すには、反対のRakeタスクを実行します:gitlab:db:unlock_writes
.
テーブルの切り捨て
データベースmain
とci
が完全に分割されたら、テーブルを切り捨てることでディスク領域を解放することができます。この結果、データセットが小さくなります:例えば、CIデータベースのusers
テーブルのデータは、もはや読み込まれることも、更新されることもありません。そのため、テーブルを切り捨てることで、このデータを削除することができます。
この目的のために、GitLabは2つのRakeタスクを提供しています:
-
gitlab:db:truncate_legacy_tables:main
はMainデータベースのCIテーブルを切り詰めます。 -
gitlab:db:truncate_legacy_tables:ci
はCIデータベースのメインテーブルを切り捨てます。
DRY_RUN=true
. DRY_RUN=true
NET Frameworkを使用しています。DRY_RUN=true
これにより、実際にデータが切り捨てられることはありません。GitLabは、.NETを使わずにこれらのタスクを実行する前にバックアップを用意しておくことを強く推奨 DRY_RUN=true
します。これらのタスクには、実際にデータを変更することなく、タスクの実行内容を確認できるオプションがあります:
$ sudo DRY_RUN=true gitlab-rake gitlab:db:truncate_legacy_tables:main
I, [2023-07-14T17:08:06.665151 #92505] INFO -- : DRY RUN:
I, [2023-07-14T17:08:06.761586 #92505] INFO -- : Truncating legacy tables for the database main
I, [2023-07-14T17:08:06.761709 #92505] INFO -- : SELECT set_config('lock_writes.ci_build_needs', 'false', false)
I, [2023-07-14T17:08:06.765272 #92505] INFO -- : SELECT set_config('lock_writes.ci_build_pending_states', 'false', false)
I, [2023-07-14T17:08:06.768220 #92505] INFO -- : SELECT set_config('lock_writes.ci_build_report_results', 'false', false)
[...]
I, [2023-07-14T17:08:06.957294 #92505] INFO -- : TRUNCATE TABLE ci_build_needs, ci_build_pending_states, ci_build_report_results, ci_build_trace_chunks, ci_build_trace_metadata, ci_builds, ci_builds_metadata, ci_builds_runner_session, ci_cost_settings, ci_daily_build_group_report_results, ci_deleted_objects, ci_editor_ai_conversation_messages, ci_freeze_periods, ci_group_variables, ci_instance_variables, ci_job_artifact_states, ci_job_artifacts, ci_job_token_project_scope_links, ci_job_variables, ci_minutes_additional_packs, ci_namespace_mirrors, ci_namespace_monthly_usages, ci_partitions, ci_pending_builds, ci_pipeline_artifacts, ci_pipeline_chat_data, ci_pipeline_messages, ci_pipeline_metadata, ci_pipeline_schedule_variables, ci_pipeline_schedules, ci_pipeline_variables, ci_pipelines, ci_pipelines_config, ci_platform_metrics, ci_project_mirrors, ci_project_monthly_usages, ci_refs, ci_resource_groups, ci_resources, ci_runner_machines, ci_runner_namespaces, ci_runner_projects, ci_runner_versions, ci_runners, ci_running_builds, ci_secure_file_states, ci_secure_files, ci_sources_pipelines, ci_sources_projects, ci_stages, ci_subscriptions_projects, ci_trigger_requests, ci_triggers, ci_unit_test_failures, ci_unit_tests, ci_variables, external_pull_requests, p_ci_builds, p_ci_builds_metadata, p_ci_job_annotations, p_ci_runner_machine_builds, taggings, tags RESTRICT
タスクはまず、切り捨てが必要なテーブルを見つけます。1回のデータベーストランザクションで削除されるデータ量を制限する必要があるため、切り捨てはステージで行われます。テーブルは外部キーの定義に応じて特定の順序で処理されます。1つのステージで処理されるテーブルの数は、タスクを呼び出すときに数値を追加することで変更できます。デフォルト値は5です:
sudo DRY_RUN=true gitlab-rake gitlab:db:truncate_legacy_tables:main\[10\]
UNTIL_TABLE
変数を設定することで、切り捨てられるテーブルの数を制限することも可能です。例えばこの場合、ci_unit_test_failures
が切り捨てられた時点で処理が停止します:
sudo DRY_RUN=true UNTIL_TABLE=ci_unit_test_failures gitlab-rake gitlab:db:truncate_legacy_tables:main