複数のデータベース

GitLabをさらに拡張できるようにするために、私たちはGitLabアプリケーションデータベースを複数のデータベースに分割しました。二つのデータベースはmainciです。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_migrationsar_internal_metadata など)やPostgreSQLの内部テーブル(pg_attributeなど)が記述されています。その主な目的は、Geoのような他のデータベースをサポートすることです。そのようなデータベースには、アプリケーション定義のgitlab_shared テーブル (loose_foreign_keys_deleted_recordsのような) がない可能性がありますが、有効なRailsデータベースです。

の特別な目的はgitlab_pm

gitlab_pm には、公開リポジトリを記述するパッケージメタデータが格納されます。このデータは、ライセンスコンプライアンスと依存関係スキャンの製品カテゴリで使用され、コンポジション分析グループによってメンテナーされています。gitlab_main のエイリアスで、将来的に別のデータベースへのルーティングを容易にすることを目的としています。

マイグレーション

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

CI/CD データベース

単一データベースの設定

デフォルトでは、GDKは複数のデータベースで動作するように設定されています。

caution
同じ開発インスタンス内で単一データベースと複数データベースを行ったり来たりすることは推奨されません。単一データベースモードでは、ci データベース内のすべてのデータにアクセスできません。単一データベースの場合は、別の開発インスタンスを使用してください。

単一データベースを使用するようにGDKを設定するには、以下の手順に従います:

  1. GDKのルートディレクトリで

    gdk config set gitlab.rails.databases.ci.enabled false
    
  2. GDKを再構成します:

    gdk reconfigure
    

複数のデータベースを使用するように戻すには、gitlab.rails.databases.ci.enabledtrue に設定し、gdk reconfigureを実行します。

テーブルci 間および ciテーブルci 以外の ci結合の削除

データベースをまたいで結合するクエリはエラーになります。GitLab 14.3 で導入された、新しいクエリのみ。既存のクエリはエラーになりません。

GitLabは複数の別々のデータベースで実行できるため、一つのクエリでci テーブルと ciテーブルci 以外を ci参照することはできません。そのため、SQLクエリでJOIN

データベースをまたいだ結合を削除するための提案

以下のセクションは、データベースをまたいだ結合が確認された実際の例と、その修正方法に関する提案です。

コードの削除

これまで何度か見てきた最も単純な解決策は、使われていない既存のスコープです。これは最も簡単に解決できる例です。ですから、最初のステップは、そのコードが未使用かどうかを調査し、それを削除することです。これらは実際の例です:

このコードが使われている例はもっとあるかもしれませんが、そのコードが必要かどうか、あるいはその機能がこのように振る舞うべきかどうかを評価することができます。新しい列やテーブルを追加して物事を複雑にする前に、ソリューションを単純化しても要件を満たせるかどうかを検討してください。評価されるケースの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を経由して結合している場合です。CBの外部キーに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)

冗長な結合を削除するこれらの例はどちらもクロス結合を削除しますが、よりシンプルで高速なクエリを作成できるという利点があります。

限定的な摘出とそれに続く検索

pluckpick を使ってids の配列を取得するのは、 返される配列のサイズが有限であることが保証されていない限りお勧め idできません。id通常、これは結果が最大で 1 になることがわかっている場合や、メモリ上の ID (あるいはユーザ名) のリストを同じサイズの別のリストにマップする必要がある場合に有効なパターンです。一対多のリレーションで ID のリストをマップする場合は、結果が無制限になるため適していません。返された ids を使って、関連レコードを取得することができます:

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がデータベースクエリのパフォーマンスを向上させるためにしばらくの間使ってきた一般的なテクニックです。上記の問題は、以下の条件を満たすことで緩和されます:

  1. データが少ない (たとえば整数カラムだけ)。
  2. データが頻繁に更新されない (例えば、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_scansproject_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_idci_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_joinshas_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 をテストしています。mainci の両方のデータベースを使います。しかし、マージリクエストでは、たとえばデータベース関連のコードを修正したり MR に~"pipeline:run-single-db" というラベルを追加したりするときに、single-dbsingle-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" というラベルを追加すると、パイプラインはmaincimain_clusterwide の3つの接続で実行されます。

note
この設定はまだ完全にはできておらず、この設定でパイプラインを実行すると、一部のジョブが失敗する可能性があります。2023年7月現在、この設定はGroup::tenant scaleCells をビルドする際に変更をテストするためだけに使用されています。

データベーススキーマに属さないテーブルへの書き込みのロック

CIデータベースが昇格させられ、2つのデータベースが完全に分割されたとき、スプリットブレインの状況を作らないための特別な安全策として、Rakeタスクgitlab:db:lock_writes を実行してください。このコマンドは書き込みをロックします:

  • CI データベースのgitlab_main テーブル。
  • メインデータベースのgitlab_ci テーブル。

このRakeタスクはすべてのテーブルにトリガーを追加し、ロックが必要なテーブルに対してINSERTUPDATEDELETETRUNCATE ステートメントが実行されないようにします。

このタスクが、gitlab_maingitlab_ci のテーブルに対して単一のデータベースのみを使用する GitLab セットアップに対して実行された場合、テーブルはロックされません。

オペレーションを元に戻すには、反対のRakeタスクを実行します:gitlab:db:unlock_writes.

テーブルの切り捨て

データベースmainci が完全に分割されたら、テーブルを切り捨てることでディスク領域を解放することができます。この結果、データセットが小さくなります:例えば、CIデータベースのusers テーブルのデータは、もはや読み込まれることも、更新されることもありません。そのため、テーブルを切り捨てることで、このデータを削除することができます。

この目的のために、GitLabは2つのRakeタスクを提供しています:

  • gitlab:db:truncate_legacy_tables:main はMainデータベースのCIテーブルを切り詰めます。
  • gitlab:db:truncate_legacy_tables:ci はCIデータベースのメインテーブルを切り捨てます。
note
これらのタスクは、データベースのテーブルが書き込みロックされている場合にのみ実行できます。
caution
このセクションの例ではDRY_RUN=true. DRY_RUN=trueNET 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