トップレベルのネームスペースによる GitLab のシャーディング

トップレベルのネームスペースによる GitLab のシャーディング

このドキュメントは、GitLab をトップレベルのネームスペースでシャーディングするというアイデアをまとめたものです。ネームスペースシャーディングにより製品機能がどのように変わらなければならないかのアイデアを提供し、このアプローチで予想される困難と複雑さを浮き彫りにするとともに、実装の詳細と既知の不明点についても触れることを目的としています。

命名規則 - ネームスペースとは何か?

まず共通の命名規則を定義しましょう。

ネームスペース

GitLab では、プロジェクトはネームスペースの中に含まれています。ネームスペースは別のネームスペースの中にネストできます。これにより、ツリー状の階層が形成されます: ネームスペースはツリーのノードであり、プロジェクトは葉です。この階層のルートを「トップレベルネームスペース」と呼びます。

この例を分解してみましょう: https://gitlab.com/gitlab-org/database-team/team-tasks

  1. gitlab-org: トップレベルネームスペース
  2. database-team: gitlab-org 内のグループ
  3. team-tasks: database-team 内のプロジェクト

サインアップ後、各ユーザーは gitlab.com/$user 配下に独自のネームスペースを持ちます。また、ユーザーはインスタンス上の任意の数の(ルート)ネームスペースと対話できます。

データアクセスパターン

データアクセスパターンとは、データがどのように参照されるかについてよく見られる視点です。これは通常、データベースクエリのフィルターがどのような形になるかを示します。いくつかの例を見てみましょう。

  1. 時間によるデータ: 主なクエリフィルターはおそらく日付範囲です。GitLab の例: インスタンス全体の監査ログ
  2. ユーザーによるデータ: 主なクエリフィルターはユーザー ID です。GitLab の例: ユーザーダッシュボード
  3. ネームスペースによるデータ: 主なクエリフィルターはネームスペース ID です。GitLab の例: Issue のグループ検索

トップレベルのネームスペースによるシャーディング

このアイデアの要点は、それぞれのトップレベルのネームスペースによってすべてのデータを水平方向にスライスすることです。

/gitlab-org/gitlab-com を2つのトップレベルのネームスペースの例として使いましょう。どちらにも多くのサブグループ、プロジェクト、大量の Issue、マージリクエスト、CI データなどがあります。データベースレベルでシャーディングを適用すると、これらの2つのネームスペースをデータベース内の別々のシャードに分離します(例のために)。

  1. シャード 1: /gitlab-org とそのすべてのデータ
  2. シャード 2: /gitlab-com とそのすべてのデータ

データ分布の詳細を今は省略すると、この2つのシャードを物理的に別々のデータベースと考えることができます。

望ましいとおり、各シャードはシャーディングされていないデータベースと比較してデータサイズがはるかに小さくなります。これにより、このシャーディングのアイデアを中心に設計されたデータベースクエリのパフォーマンスが大幅に向上します。詳細については「シャーディングをサポートする機能」を参照してください。

一方、厳密に言えば、これはデータへのアクセスが常に(ルート)ネームスペースにスコープされなければならないという意味で製品機能を制限します。そうでなければ、すべてのシャードをスキャンしなければならなくなり、それは望ましくなく、スケールすると破綻します。これはアクセスパターンと、一部の製品機能がデータにどのようにアクセスするかについてです。詳細については「シャーディングと競合する機能」を参照してください。

事態を複雑にするのは、ネームスペースとは全く無関係なデータがあることです。データベーススキーマを静的に分析することでこれがわかります。アクセスパターンをこの時点では考慮しなくても、このタイプのデータはネームスペースによるシャーディングができないと既に結論付けることができます。ネームスペースへの参照がありません。

詳細に入る前に、このシャーディングアプローチに理想的なものを簡単にまとめましょう。

  1. 関連するデータ(1つのデータベースクエリで一般的に一緒にアクセスされるデータ)に対して、共通のシャーディングディメンションが1つだけある。
  2. シャード可能なデータには、シャーディングキーの何らかの概念がある。たとえば、ネームスペースへの参照。これは推移的なプロパティである可能性がある。たとえば、Issue にはトップレベルのネームスペースへの直接の参照はなく、プロジェクト内に含まれ、おそらく他のグループを経由して最終的にトップレベルのネームスペースを参照する。
  3. 機能は、データアクセスパターンがシャーディングディメンションと一致するように設計されている。

以下のセクションでは、これらの点を GitLab に適用して詳述します。

GitLab における共通シャーディングディメンション

データベースのシャーディングをトップレベルのネームスペースで説明しており、これはアプリケーション全体でトップレベルのネームスペースを共通のシャーディングディメンションとして使用するという前提の上に成り立っています。これが GitLab でどれだけうまく機能するかを見てみましょう。

データベーススキーマ

GitLab の現在のデータベーススキーマには、ネームスペースの概念を持たない約 80 個(350 個中)のテーブルがあります。これはデータベーススキーマを静的に分析し、(おそらく推移的に)ネームスペースに関連するカラムを確認した結果です。この分析の概要は「ネームスペースによるシャーディングにおける技術的複雑さについてのメモ」(GitLab 内部リンク)に記載されています。

データサイズの観点では、ネームスペースの概念がないテーブルは現時点で約 400 GB のデータを占めています(総サイズの約 6%)。100 倍の成長を予想すると、シャーディングできないデータが 40 TB になります。実装の詳細によっては、このデータは各シャードにローカルに公開するためにレプリケートするか、中央コーディネーターインスタンスのみで管理する必要があります(その場合、クエリでシャードからのローカルデータとリモートデータを組み合わせる必要があります)。

マルチマスター書き込み機能(つまり、異なる場所で同じデータを書き込む機能)を追加しないと仮定すると、これにより読み書きキャパシティのスケールアップ能力が制限されます。これらの書き込みは単一のクラスターでのみ発生できるためです。

機能とアクセスパターン

シャーディングにはシャーディングキーの概念が必要ですが、それだけでは実際に利益を得るのに十分ではありません。シャーディングを活用するために、機能はシャーディングディメンションによってシャーディングされたデータにアクセスする必要があります。追加のフィルターを加えることは問題ありませんが、シャーディングキーがなければシャーディングを活用していないだけでなく、クエリパフォーマンスの潜在的に深刻な劣化が予想されます: シャーディングキーなしでは、すべてのシャードをスキャン(ファンアウト)しなければなりません。これは非常にコストがかかります。実装の詳細によっては、並列クロスシャードスキャンの実装が困難かもしれません。

ここでは2つの例を見てみましょう。より多くの例は #50 にあります。

  1. Issue: これはネームスペース内で生まれるが、アクセスパターンが競合するデータの代表的な例です。
  2. 監査ログ: これはインスタンスレベルで競合するアクセスパターンを持つ機能の代表的な例です。
Issue

Issue トラッカーは単一のプロジェクト内に存在します。そのため、Issue データは推移的にトップレベルのネームスペースを参照します。

ネームスペースによるシャーディングはこれらの機能に理想的です。

  1. 検索などを含むプロジェクト Issue トラッカーのリストと操作(
  2. グループレベルも同様(

これらの例では、アクセスパスが明らかに「ネームスペースによる」または「プロジェクトによる」(最終的には「トップレベルのネームスペースによる」がカバーする)であることがわかります。

一方、クロスシャードスキャンが必要になる機能があります。

  1. ユーザーダッシュボード: 特定のユーザーに割り当てられた Issue はどれか?
  2. 同様: 作成者、担当者、ラベル、マイルストーン、リリース、「自分のリアクション」によるインスタンス全体の Issue 検索。

これらの機能はネームスペースによるシャーディングのアイデアと競合するアクセスパターンを持っています。この結果、すべてのシャードにまたがるデータベースクエリが発生します。概念的には、ユーザーがインスタンス上の任意の数のネームスペースと対話できるという事実から来ています。したがって、割り当てられた Issue を見つけるには、すべてのネームスペースを確認する必要があります。

シャーディングされていないデータベースでは、これは簡単です: N:M の関係をモデル化するために単一のルックアップテーブル issue_assignees (user_id int, issue_id int) を維持します。

ネームスペースでシャーディングされたデータベースでは、前の例に従って2つのシャードがあります。

  1. シャード 1: /gitlab-org のテーブル issue_assignees (user_id int, issue_id int)
  2. シャード 2: /gitlab-com データのテーブル issue_assignees (user_id int, issue_id int)

割り当てられたすべての Issue を見つけるために、すべてのシャードを反復してそれぞれをクエリします。ユーザーがどのトップレベルのネームスペースと対話したかを全く知らないため、Issue の割り当てをどこで探すかがわからないためです(「ユーザーインタラクションの統計」も参照)。すべてのシャードをクエリした後、すべてのシャードにわたって(何らかの基準で)上位 20 の Issue を表示するために結果を照合します。

これをどうすれば解決できるか?

ネームスペースシャーディングの理想的なシナリオでは、これらの機能はネームスペースによって制約されます。つまり、検索は複数のネームスペースにわたって実行できません。これを実装するために理論的に2つの可能な方法があります。

  1. 常にユーザーに検索のための希望するネームスペースを選択させる
  2. より全体的なレベルで、単一のユーザーを一度に1つのネームスペース内のデータのみを操作・閲覧するよう制限する

どちらの場合も、現在アクティブなネームスペースを変更することが可能です。しかし、どちらの機能も複数のネームスペースにわたるデータを表示または操作することはできません。

監査ログ

監査ログには、インスタンス全体で発生したイベントに関する情報が含まれています。イベントはユーザー、プロジェクト、またはグループに関連している可能性があります。

それに基づいて、プロジェクトとは無関係なイベントがあることが既にわかります: ユーザー関連のイベントにはネームスペースの概念が全くありません。GitLab.com では、これは監査ログデータの約 75% を占めており、ネームスペースによってシャーディングできません。

ユーザーは異なる視点から監査ログを操作できます。

  1. プロジェクトによる
  2. グループによる(現在フィーチャーフラグの後ろ)
  3. インスタンスレベルでのグローバル(管理者向け)

グローバルなインスタンスレベルのビューが問題です: 理想的には、インスタンス管理者はインスタンス上のすべてのネームスペースとユーザーにわたって何が起こったかを確認したいです。

要約すると、ネームスペースでシャーディングした場合の課題は以下のとおりです。

  1. データの 75% がこのディメンションではシャーディングできない
  2. ネームスペースシャーディングはインスタンスレベルのビューをサポートしない
これをどうすれば解決できるか?

監査ログ機能とそのデータは、管理者機能を変更しない限り、現在の形ではネームスペースシャーディングには適していません。

たとえば、管理者が常に関心のあるネームスペースを選択し、一度に単一のネームスペースのデータのみを表示するようにすることができます。

しかし、ユーザーの監査ログを表示するためには、このビューを提供するために大きな非シャーディングテーブルを構築し続けることしかできません。監査ログ機能がアプリケーションの残りの部分からどれだけ分離されているかによっては、異なるシャーディングディメンション(時間またはユーザー)の実現可能性について検討することもあります。

GitLab.com: ユーザーインタラクションの統計

ネームスペースによるシャーディングの前提は、「顧客は自分のネームスペース内で起こることだけを気にする」というアイデアです。

たとえば、GitLab.com の SaaS 顧客にとって、組織外のユーザーを誤って @メンションすることは望ましくない場合もあります。典型的な SaaS 顧客は、たとえば自分のネームスペース内の検索結果だけを気にすると想定されます。

GitLab では、ユーザーがどのプロジェクトと対話したかを追跡しています。それに基づいて、GitLab.com 上でユーザーが対話するプロジェクト、グループ、トップレベルのネームスペースの数の分析を実施しました。ここでのインタラクションは、プロジェクトに何らかのアクションを実行することと定義されます。プロジェクトを開いて見るだけでは十分ではありません(つまり、イベントにつながるアクションを探しています)。アクティビティのないユーザーも含まれていることに注意してください。

user-interaction-middle

要約と結論は以下のとおりです。

  1. ユーザーの 95% は約 4 つ以下のトップレベルのネームスペースと対話します(自分のユーザーネームスペースを含む)。

  2. ユーザーの 60% は 1 つのトップレベルのネームスペースのみと対話します

  3. ユーザーの 45% は自分のネームスペース以外のネームスペースと対話します

  4. ユーザーの 15% は自分のネームスペース以外の複数のネームスペースと対話します

使用した生データを含む詳細は gitlab-org/database-team/user-interaction-data にあります。

これはネームスペースシャーディングアプローチにとって良いニュースですが、すべての機能の競合するアクセスパターンの根本的な問題を解決するわけではありません。

「ユーザーによる」アクセスパターンについては、この情報を使用してまず特定のユーザーに関連するシャードを特定し、それらのみをクエリすることができるかもしれません。これにより、すべてのシャードにわたるスキャンを避け、関連するものに近づけることができます。しかし、これはもはや静的分析ではなく、特定のユーザーにとって関連するシャードを追跡するルックアップ情報の維持が必要です。

データベースシャーディングの技術的実装

GitLab は PostgreSQL 上に構築されており、PostgreSQL はネイティブシャーディングをサポートしていません(ただし、コミュニティのロードマップには含まれています)。代わりに CitusDB の使用を検討しましたが、私たちには適していませんでした。

シャーディングデータベース機能の概念を自分たちで実装するために、外部データラッパーとパーティショニングを組み合わせた PostgreSQL 機能の使用も検討しました。これを実装できる主要な方向性を特定しましたが、その複雑さも十分に認識しています。これは「つまらない」解決策とは見なされておらず、この道を進む他の企業からの好意的な報告を知りません。また、GitLab Geo での FDW 使用の自社経験に基づいても、これは非常に複雑なアプローチだと考えます。

特定された複雑さは以下のトピックに関係しています。

  1. 運用: GitLab.com で複数の相互接続された PostgreSQL クラスターとコーディネータークラスターを実行する必要があります(それぞれに独自の HA セットアップが必要で、コーディネーターは単一障害点です)。
  2. 開発: PostgreSQL FDW は FDW を通じた並列スキャン(ファンアウト)をサポートしていません。スケールでのすべてのシャードのスキャンは実現不可能に思われます。
  3. 開発: スキーママイグレーションは分散データベースでは達成が困難です。
  4. 運用: データベース状態の一貫したバックアップは問題があります。
  5. スケーラビリティ: 単一点からレプリケートされたテーブルの書き込みボトルネック(シャーディングできないデータの場合)
  6. 製品: 多くの製品機能がこのアプローチで非常に遅くなる/コストが高くなる、または壊れる可能性があると特定されています。機能がネームスペース内に完全に含まれるよう、製品の考え方を合わせる必要があります。理想的には、ユーザーを単一のネームスペースに閉じ込めることも含まれますが、これは大きな取り組みであり、ユーザーエクスペリエンスを大幅に制限するように思われます。

シャーディング機能が必要なのは GitLab.com のみであり、セルフマネージドのインストールには必要ないと予想しています。これは実装をさらに複雑にします。GitLab.com 固有のアクションを実装しなければならない可能性が高いためです。

要約

データベースシャーディングに何が必要かを探求し、GitLab でこれがどれほどうまく機能するかのアイデアも得ました。ネームスペースベースのシャーディングを成功裏に実装するために、2つの課題を特定しました。

  1. 製品機能をネームスペースベースのシャーディングに合わせる - これには、ネームスペースの境界を念頭に置いて製品の多くの再考が必要で、新しい機能を設計し(また既存の機能を再設計し)することが予想されます。
  2. 分散データベースバックエンドの技術的実装 - どのように見えるかのアイデアはありますが、リスクが高く長い取り組みになるようです。

これはパーティショニングの取り組みとどう関係するか?

私たちはアプリケーションに PostgreSQL テーブルパーティショニングを導入することを目指しています。これは、アプリケーションのパフォーマンスとスケーラビリティを大幅に改善するためのデータベースツールと考えることができます。

単一の機能にローカルに適用して、特定のユースケースに最適化できます。たとえば、パーティショニングパターンを使用した監査ログ機能の改善に取り組んでいます。このローカリティからの恩恵は、アプリケーション全体でさまざまなケースに最適化できることです。すべてに対して単一の共通シャーディングディメンションを見つける必要はありません。

パーティショニングを探索してシャーディングの概念を実装してきましたが(FDW で)、パーティショニングはデータベースインタラクションを改善するシンプルなツールでもあります。そのため、シャーディングアプローチに加えて使用できます: シャーディングされたデータベースやアプリケーションシャーディングでは、パーティショニングはシャードにローカルであり、データベーススケーラビリティをさらに改善します。

その意味で、パーティショニングは今すぐ活用できるツールであり、運用の複雑さをもたらしません。データベースのパフォーマンスとスケーラビリティを直接改善します。GitLab.com では現在、パフォーマンスの問題に対処するための適切なツールと思われるスケールにいます。さらに、データを水平方向にスライスし、既存のアクセスパターンを分析する(そして製品機能をより合わせるよう再考する可能性もある)という同じ根本的な考え方を持ち合わせています。

パーティショニングに関連するリンク:

  1. テーブルパーティショニングについて
  2. テーブルパーティショニング: Issue のグループ検索を例として
  3. データベースパーティショニング epic

これからどこへ向かうか

データベーススケーラビリティを改善する方法を引き続き探求し、特に GitLab.com を念頭に置きます。

  1. 体系的なワークロード分析、クエリベースのマイクロ分析、モデリング・クエリ構築・インデックス・パーティショニングなどの標準的なリレーショナルベストプラクティスの適用 - 手の届きやすい成果を収穫する()。
  2. 中期的なデータベースキャパシティ分析が進行中で、スケーラビリティニーズの必要性とタイムラインをより良く把握するためのものです。短期的な推定は間もなく最初の結果をもたらすことが期待されています。
  3. データ保持戦略を実装してデータベースのサイズと成長を削減する
  4. スケールアップはまだ可能に見えます
    1. 読み書きワークロード: 必要に応じてプライマリの読み書きキャパシティを改善するために引き続き垂直スケールアップできます。
    2. 読み取りワークロード: 複数のレプリカに読み取りワークロードをスケールアウトし続けます。

これとは別に、シャーディングアプローチについて引き続き議論します。これは最初は主に製品の問題です。製品の変更を必要とせずにこれがうまくいく可能性は非常に低いためです。


著者: Andreas Brandl