Status | Authors | Coach | DRIs | Owning Stage | Created |
---|---|---|---|---|---|
proposed | - |
- 相違点
- 図でのまとめ
- 変更の概要
- 飛行前のリクエスト学習
- 最初の反復におけるデフォルトの構成についての詳細な説明
- 管理エリア設定の詳細
- 長所
- 短所
- データベースの設定例
- リクエストフロー
- 管理者
- 解決すべきその他の技術的問題
- ランナーはすべてのセルで共有されるべきですか?
- すべてのセルで競合しない一意のIDを保証するには?
このドキュメントは作業中のものであり、セルズの設計のごく初期の状態を表しています。重要な点は文書化されていませんが、将来的には追加される予定です。これはCellsの可能性のあるアーキテクチャの一つであり、どのアプローチを実装するか決める前に、代替案と比較検討するつもりです。この文書化は、このアプローチを選ばなかった理由を文書化できるよう、これを実装しないと決めた場合でも残しておきます。
提案ステートレス・ルーター
gitlab_users
,gitlab_routes
,gitlab_admin
の関連テーブルを分解して、すべてのセル間で共有できるようにし、どのセルでもユーザーを認証してリクエストを正しいセルにルーティングできるようにします。セルは自分が所有していないリソースに対するリクエストを受け取るかもしれませんが、正しいセルにリダイレクトする方法を知っています。
ルータはステートレスで、routes
データベースから読み取ることはありません。つまり、データベースとのやり取りはすべて Rails モノリスから行われます。このアーキテクチャは、トラフィックの少ないデータベースを地域間で複製できるようにすることで、地域もサポートしています。
ユーザーは直接Cellsの概念に触れることはありませんが、代わりに選択したOrganizationsによって異なるデータを見ることができます。Organizationsはアプリケーションの分離を強化するために導入される新しいエンティティで、Organizationは1つのCellにしか存在できないため、どのリクエストをどのCellにルーティングするかを決めることができます。
相違点
この提案とリクエストをバッファリングする提案の主な違いは、この提案がリクエストボディを正しいCellにリダイレクトするためにプリフライトAPIリクエスト(/api/v4/cells/learn
)を使うことです。つまり、各リクエストは正確に一度だけ送られて処理されますが、URIはどのCellに送られるべきかをデコードするために使われます。
図でのまとめ
これは、ユーザーのリクエストがDNS経由で最も近いルーターにルーティングされ、ルーターがリクエストを送信するセルを選択する様子を示しています。
詳細
これは、ルーターが実際にどのセルにもリクエストを送信できることを示しています。ユーザーは地理的に最も近いルーターを取得します。
さらに詳細
これはデータベースを示しています。gitlab_users
とgitlab_routes
は米国リージョンにのみ存在しますが、他のリージョンにもレプリケートされます。レプリケーションに矢印がないのは、図を読むのが難しいからです。
変更の概要
- ユーザーデータ(プロファイル設定、認証情報、個人アクセストークンを含む)に関連するテーブルが、
gitlab_users
スキーマに分解されました。 -
routes
テーブルはgitlab_routes
スキーマに分解されます。 -
application_settings
(およびおそらく他のいくつかのインスタンスレベルのテーブル)は、gitlab_admin
スキーマに分解されます。 - 新しいカラム
routes.cell_id
がroutes
テーブルに追加されます。 - どのセルにリクエストをルーティングするかを選択する新しいルーターサービスが存在します。
- ルーターが新しいリクエストを受信すると、
/api/v4/cells/learn?method=GET&path_info=/group-org/project
。 - GitLabに組織という新しい概念が導入されます。
- 既存のすべてのエンドポイントをURIでルーティングできるようにするか、処理のために特定のCellに固定する必要があります。そのためには、
/dashboard
のような曖昧なエンドポイントを次のようなスコープに変更する必要があります。/organizations/my-organization/-/dashboard
-
/admin
のようなエンドポイントは常に特定のセルにルーティングされます。cell_0
- 各セルは
/api/v4/cells/learn
に応答し、各エンドポイントを分類することができます。 -
gitlab_users
とgitlab_routes
への書き込みはUS
リージョンのプライマリ PostgreSQL サーバに送信されますが、読み込みは同じリージョン内のレプリカから行うことができます。このため、これらの書き込みにはレイテンシが発生しますが、GitLabの他の部分と比較すると発生頻度は低いと思われます。
飛行前のリクエスト学習
リクエストを処理している間、URIはデコードされ、プリフライトリクエストが各非キャッシュエンドポイントに対して送信されます。
エンドポイントを要求するとき、GitLab Railsはルーティング可能なパスに関する情報を返します。GitLab Railsはpath_info
をデコードして既存のエンドポイントとマッチさせ、ルーティング可能なエンティティ(プロジェクトのような)を見つけます。ルータはこれを短命のキャッシュ情報として扱います。
-
プレフィックスマッチ:
/api/v4/cells/learn?method=GET&path_info=/gitlab-org/gitlab-test/-/issues
{ "path": "/gitlab-org/gitlab-test", "cell": "cell_0", "source": "routable" }
-
エンドポイントによっては完全一致が必要な場合があります:
/api/v4/cells/learn?method=GET&path_info=/-/profile
{ "path": "/-/profile", "cell": "cell_0", "source": "fixed", "exact": true }
最初の反復におけるデフォルトの構成についての詳細な説明
すべてのユーザーは、ユーザー設定でコントロールできる新しいカラムusers.default_organization
。GitLab.com Public
組織という概念を導入します。これは、すべての既存ユーザーのデフォルトの組織として設定されます。この組織は、Cell US0
(つまり、私たちのオリジナルのGitLab.comインスタンス)のすべての名前空間のデータをユーザーに見せることができます。この振る舞いは、/dashboard
のようなグローバルページを表示しているときに、それが組織にスコープされていることすら知らされないように、既存のユーザーには見えないようにすることができます。
GitLab.com Public
以外のデフォルト組織を持つ新規ユーザーは、異なるユーザーエクスペリエンスを持つことになり、読み込むすべてのページが単一の組織にのみスコープされていることを完全に認識することになります。このようなユーザーは、/dashboard
のようなグローバルページを読み込むことができず、/organizations/<DEFAULT_ORGANIZATION>/-/dashboard
にリダイレクトされてしまいます。これはレガシーAPIの場合も同様で、このようなユーザーは組織にスコープされたAPIしか使用できない可能性があります。
管理エリア設定の詳細
私たちは、管理エリアの設定を維持したり同期させたりすることは、イライラさせ、苦痛を伴うと信じています。そのため、これを避けるために、すべての管理エリアの設定を分解し、gitlab_admin
スキーマで共有します。このスキーマは書き込みトラフィックがほとんどないため、(他の共有スキーマと同様に)安全です。
異なるセルが異なる設定を必要とする場合(例えばElasticsearchのURL)、関連するapplication_settings
の行でテンプレート化されたフォーマットを使用することにします。また、それが難しい場合は、per_cell_application_settings
という新しいテーブルを導入し、セルごとに異なる設定を行えるようにします。このテーブルもgitlab_admin
スキーマの一部として共有され、一元的に管理することができます。
長所
- ルーターはステートレスで、多くの地域に住むことができます。エニーキャストDNSを使用して、ユーザーに最も近い地域に解決します。
- セルは間違ったセルの名前空間に対するリクエストを受信しても、ユーザーは正しいレスポンスを得ることができます。また、次のリクエストが正しいセルに送信されるようにするルータでのキャッシュにより、次のリクエストは正しいセルに送信されます。
- コードの大部分はまだ
gitlab
Rails コードベースにあります。ルーターは GitLab の URL がどのように構成されているかを理解する必要はありません。 -
gitlab_users
、gitlab_routes
、gitlab_admin
を読み書きする責任はまだ Rails にあるので、ドメインモデルを分離して新しいインターフェースを構築する必要があるサービスを抽出するのに比べて、Rails アプリケーションには最小限の変更で済むことになります。 - 別個のルーティングサービスと比べて、RailsアプリケーションはURLを正しいセルにマッピングする方法についてより複雑なルールをエンコードすることができ、既存のAPIエンドポイントでも機能する可能性があります。
- 新しいインフラ (ルーターだけ) はすべてオプションで、シングルセルのセルフマネージドインストールではルーターを実行する必要すらなく、他に新しいサービスもありません。
短所
-
gitlab_users
gitlab_routes
、gitlab_admin
データベースはリージョンをまたいで複製する必要があり、書き込みはリージョンをまたぐ必要があるかもしれません。実現可能かどうかを判断するために、関連テーブルの書き込みTPSを分析する必要があります。 - 多くの異なる Cell からデータベースへのアクセスを共有するということは、すべての Cell が Postgres スキーマレベルで結合されるということであり、これはデータベーススキーマの変更をすべての Cell のデプロイと同期して慎重に行う必要があることを意味します。このため、私たちがコントロールする API を持つ共有サービスによるアーキテクチャと比較して、Cell を密接に類似したバージョンに保つことが制限されます。
- ほとんどのデータは適切なリージョンに保存されますが、別のリージョンからプロキシされるリクエストもあり、これはある種のコンプライアンスにとってイシューになる可能性があります。
-
gitlab_users
、gitlab_routes
データベースのデータは、すべてのリージョンで複製される必要があり、これは特定の種類のコンプライアンスにとってイシューとなる可能性があります。 - 多種多様なURL(つまりロングテール)を取得する場合、ルーターキャッシュは非常に大きくする必要があるかもしれません。そのような場合、ユーザークッキーに第2レベルのキャッシュを実装し、頻繁にアクセスされるPagesが常に最初に正しいセルに行くようにする必要があるかもしれません。
- 複数のセルから
gitlab_users
、gitlab_routes
、共有データベースアクセスを持つことは、複数のセルから呼び出されるサービスを抽出することと比べると、珍しいアーキテクチャの決定です。 - GraphQL URLのキャッシュ可能な要素を見つけることができない可能性が非常に高く、既存のGraphQLエンドポイントは
routes
テーブルにないidに大きく依存していることが多いため、セルはどのセルがデータを持っているかを必ずしも知ることができません。そのため、/api/organizations/<organization>/graphql
のようにパスに組織のコンテキストを含めるようにGraphQLコールを更新する必要があるでしょう。 - このアーキテクチャは、実装されたエンドポイントが、与えられたセル上で容易にアクセスできるデータにのみアクセスできることを意味し、多くのセルからの情報を集約することは考えにくい。
- 未知のルートはすべて最新のデプロイに送信されます。
Cell US0
と仮定します。これは、新しく追加されたエンドポイントは最新のセルにしか解読できないためです。これは/cells/learn
にとって問題ではなさそうですが、処理が軽量であるため、パフォーマンスへの影響はないはずです。
データベースの設定例
gitlab_users
、gitlab_routes
、gitlab_admin
の共有データベースを扱いながら、gitlab_main
、gitlab_ci
の専用データベースを持つことは、config/database.yml
を使用する方法ですでに処理できるはずです。また、gitlab_users
とgitlab_routes
に対して単一の米国プライマリを持つ一方で、EU専用のレプリカを扱うこともできるはずです。以下は、上記のCellアーキテクチャのデータベース設定の一部のスニペットです。
セルUS0:
# config/database.yml
production:
main:
host: postgres-main.cell-us0.primary.consul
load_balancing:
discovery: postgres-main.cell-us0.replicas.consul
ci:
host: postgres-ci.cell-us0.primary.consul
load_balancing:
discovery: postgres-ci.cell-us0.replicas.consul
users:
host: postgres-users-primary.consul
load_balancing:
discovery: postgres-users-replicas.us.consul
routes:
host: postgres-routes-primary.consul
load_balancing:
discovery: postgres-routes-replicas.us.consul
admin:
host: postgres-admin-primary.consul
load_balancing:
discovery: postgres-admin-replicas.us.consul
セル EU0:
# config/database.yml
production:
main:
host: postgres-main.cell-eu0.primary.consul
load_balancing:
discovery: postgres-main.cell-eu0.replicas.consul
ci:
host: postgres-ci.cell-eu0.primary.consul
load_balancing:
discovery: postgres-ci.cell-eu0.replicas.consul
users:
host: postgres-users-primary.consul
load_balancing:
discovery: postgres-users-replicas.eu.consul
routes:
host: postgres-routes-primary.consul
load_balancing:
discovery: postgres-routes-replicas.eu.consul
admin:
host: postgres-admin-primary.consul
load_balancing:
discovery: postgres-admin-replicas.eu.consul
リクエストフロー
-
gitlab-org
はトップレベルの名前空間で、GitLab.com Public
組織のCell US0
に存在します。 -
my-company
はトップレベルの名前空間で、my-organization
組織のCell EU0
に存在します。
の一部である有料ユーザーのエクスペリエンスです。my-organization
このようなユーザーは、デフォルトの組織が/my-organization
に設定され、この組織以外のグローバルルートを読み込むことができません。他のプロジェクト/ネームスペースをロードすることはできますが、ページ上部のMR/ToDo/イシューのカウントは最初の反復では正しく入力されません。ユーザーはこの制限を認識します。
ログイン中に/my-company/my-project
に移動します。
- ユーザーはヨーロッパにいるため、DNSはヨーロッパのルーターに解決します。
- ルーターのキャッシュなしで
/my-company/my-project
。Cell EU1
-
/cells/learn
はCell EU1
に送られ、 はリソースが次の場所にあることを応答。Cell EU0
-
Cell EU0
に保存されている正しいレスポンスを返します。 - ルータは
/my-company/*
にマッチするリクエストパスをキャッシュし、覚えています。Cell EU0
ログインしていないときに/my-company/my-project
に移動します。
- ユーザーはヨーロッパにいるため、DNSはヨーロッパのルーターに解決します。
- ルーターは
/my-company/*
をまだキャッシュしていないので、ランダムに選択します。Cell EU1
-
/cells/learn
はCell EU1
に送られ、 はリソースが次の場所にあることを応答。Cell EU0
-
Cell EU0
ログインフローを経由して - ユーザーが
/users/sign_in
、ランダムなCellを使用して実行するように要求します。/cells/learn
-
Cell EU1
は固定ルートとしてcell_0
で応答。 - ログイン後のユーザーは
/my-company/my-project
をリクエストします。Cell EU0
-
Cell EU0
に保存されている正しいレスポンスを返します。
最後のステップの後、/my-company/my-other-project
。
- ユーザーはヨーロッパにいるため、DNSはヨーロッパのルーターに解決します。
- ルーターのキャッシュには
/my-company/* => Cell EU0
があるので、ルーターは以下を選択します。Cell EU0
-
Cell EU0
は正しいレスポンスとキャッシュヘッダを返します。
最後のステップの後、/gitlab-org/gitlab
。
- ユーザーはヨーロッパにいるため、DNSはヨーロッパのルーターに解決します。
- ルーターにはこのURLのキャッシュ値がないため、ランダムに
Cell EU0
-
Cell EU0
にリダイレクトします。Cell US0
-
Cell US0
は正しいレスポンスとキャッシュヘッダを返します。
この場合、ユーザーは「デフォルトの組織」ではないので、TODOカウンターには典型的なTODOは含まれません。このことをUIのどこかで強調表示することもできます。将来的には、デフォルトの組織からTODOを取得できるようになるかもしれません。
にナビゲートします。/
- ユーザーはヨーロッパにいるため、DNSはヨーロッパのルーターに解決します。
- ルーターに
/
ルート用のキャッシュがない(特にRailsがこのルートをキャッシュするように指示したことがない)。 - ルータは
Cell EU0
をランダムに選択します。 - Railsアプリケーションは、ユーザーのデフォルト組織が
/my-organization
であることを知っているので、ユーザーを次のようにリダイレクトします。/organizations/my-organization/-/dashboard
- ルータは
/organizations/my-organization/*
のキャッシュ値を持っているので、リクエストをPOD EU0
-
Cell EU0
にリクエストを送ります。/organizations/my-organization/-/dashboard
は現在あるのと同じダッシュボードビューですが、UIで明確に組織にスコープされています。 - ユーザーには(オプションで)、このページのデータはデフォルトの組織のものであり、デフォルトの組織が正しくない場合は変更できるというメッセージが表示されます。
にナビゲートします。/dashboard
Railsアプリケーションはすでに/
、ダッシュボードのページにリダイレクトしているため、上記のページと同様に、/organizations/my-organization/-/dashboard
。
ログインした状態で/not-my-company/not-my-project
に移動します (ただし、このプロジェクト/グループは非公開なので、アクセス権はありません)。
- ユーザーはヨーロッパにいるため、DNSはヨーロッパのルーターに解決します。
- ルーターは
/not-my-company
がCell US1
に住んでいることを知っているので、このルーターにリクエストを送ります。 - ユーザーはアクセスできないので、
Cell US1
は 404 を返します。
新しいトップレベル・ネームスペースを作成します。
ユーザは、ネームスペースをどの組織に所属させるかを尋ねられます。ユーザが選択した場合、そのネームスペースmy-organization
は .NET の他のすべてのネームスペースと同じセルに表示さ my-organization
れます。 ユーザが何も選択しなかった場合、デフォルトはGitLab.com Public
になり、ユーザはこのネームスペースが既存の組織から分離され、1 つのページで両方のデータを表示できないことがわかります。
GitLabチームメンバーの経験/gitlab-org
このようなユーザーはレガシーユーザーとみなされ、デフォルトの組織はGitLab.com Public
に設定されています。これは実際には存在しない「メタ」組織ですが、Railsアプリケーションはこの組織を解釈することで、/dashboard
のようなレガシーなグローバル機能を使用して、Cell US0
にある名前空間全体のデータを参照することが許可されていることを知っています。Railsバックエンドは、/dashboard
のような曖昧なルートをレンダリングするデフォルトのセルはCell US0
であることも知っています。最後に、ユーザーは/my-organization
のような別のセルにある組織に移動することができますが、移動すると、一部のデータが欠落している可能性があることを示すメッセージが表示されます(例:MRs/Issues/Todos)。
ログインしていないときに/gitlab-org/gitlab
に移動します。
- ユーザーは米国にいるため、DNSは米国のルーターに解決されます。
- ルーターは、
/gitlab-org
がCell US0
に住んでいることを知っているので、このセルにリクエストを送信します。 -
Cell US0
レスポンス
にナビゲートします。/
- ユーザーは米国にいるため、DNSは米国のルーターに解決されます。
- ルーターに
/
ルート用のキャッシュがない(特にRailsがこのルートをキャッシュするように指示したことがない)。 - ルーターは
Cell US1
をランダムに選択します。 - Railsアプリケーションはユーザーのデフォルト組織が
GitLab.com Public
であることを知っているので、ユーザーを/dashboards
にリダイレクトします(レガシーユーザーのみが/dashboard
グローバルビューを見ることができます)。 - ルーターに
/dashboard
ルート用のキャッシュがない(特にRailsがこのルートをキャッシュするように指示したことがない)。 - ルーターは
Cell US1
をランダムに選択します。 - Railsアプリケーションは、ユーザーのデフォルトの組織が
GitLab.com Public
であることを知っているので、ユーザーに/dashboards
(レガシーユーザーのみ/dashboard
グローバルビューを見ることができます)をロードさせ、ルーターにレガシーセルをリダイレクトします。Cell US0
-
Cell US0
はグローバルビューのダッシュボードページ/dashboard
を提供します。
ログインした状態で/my-company/my-other-project
にナビゲートします(ただし、このプロジェクトは非公開なので、彼らはアクセスできません)。
404が表示されます。
認証されていないユーザーの体験
/dashboard
のようなグローバルルートがログインページにリダイレクトされる以外は、ログインユーザーと同様のフローです。
新規顧客がサインアップ
すでに組織に属しているのか、それとも組織を作りたいのかを尋ねられます。どちらも選択しない場合は、デフォルトのGitLab.com Public
。
組織は、1つのセルから別のセルに移動されます
TODO
URLに名前空間を含まないGraphQL/APIリクエスト
TODO
最近のイシュー/MRを記憶する検索バーのオートコンプリート・サジェスト機能
TODO
グローバル検索
TODO
管理者
/admin
ページを読み込みます。
-
/admin
にロックされています。Cell US0
-
/admin
/admin/cells/cell_0/projects
のエンドポイントには、管理画面のプロジェクトのように、セルにスコープされているものがあり、ユーザーはドロップダウンで正しいものを選択する必要があります。
Postgres の Admin Area 設定は分岐を避けるためにすべてのセルで共有されますが、これらのページから生成される動的なデータとオペレータが特定のセルを表示したい場合があるため、どのセルが Admin Area ページを提供しているかを URL と UI で明確にします。
解決すべきその他の技術的問題
全セル間でのユーザーセッションの複製
現在、ユーザーセッションはRedisに保存されていますが、各セルは独自のRedisインスタンスを持つことになります。私たちはすでにセッション専用のRedisインスタンスを使っているので、gitlab_users
PostgreSQLデータベースのように、これをすべてのセルで共有することも考えられます。しかし、同じリージョンからセッションをフェッチしたいので、レイテンシを考慮することが重要です。
代替案として、ユーザーセッションをすべてのセッションデータをエンコードするJWTペイロードに移動することもできますが、これには欠点があります。例えば、セッションがユーザーによって管理されるJWTにある場合、パスワードが変更されたときやその他の理由でユーザーセッションを失効させることは困難です。
どのように Cells 間をマイグレーションするのですか?
セル間でデータをマイグレーションするには、すべてのデータストアを要因にする必要があります:
- PostgreSQL
- Redis 共有ステート
- Gitaly
- Elasticsearch
タイミング攻撃で非公開グループの存在を漏らすことは可能ですか?
EUにルーターがあり、EUのルーターがデフォルトでEUにあるCellにリダイレクトすることを知っている場合、そのレイテンシ(仮に10msとします)を知っています。今、あなたのリクエストがバウンスバックされ、異なるレイテンシを持つUSにリダイレクトされた場合(ラウンドトリップは約60ミリ秒であると仮定します)、あなたは404がUSセルによって返されたことを推測し、あなたの404が実際には403であることを知ることができます。
このことは、実際に異なる地域のセルを実装するまで延期することができます。このようなタイミング攻撃は、今日の権限チェックの方法で理論的にはすでに可能ですが、タイミングの違いはおそらく検出するには小さすぎるでしょう。
このリスクを軽減する一つのテクニックは、ルーターがセルから404を返 すリクエストにランダムな遅延を追加することかもしれません。
ランナーはすべてのセルで共有されるべきですか?
2つの選択肢がありますが、どちらが簡単か決めましょう:
- ランナー登録テーブルとキューイングテーブルを分解し、すべてのセルで共有すること。これはスケーラビリティに影響を与えるかもしれません。また、グループ/プロジェクト・ランナーを含めるかどうかを検討する必要があります。これらは共有する必要がある高トラフィック・テーブルなので、スケーラビリティの懸念があるかもしれません。
- Runnerはセルごとに登録されますが、おそらくセルごとに別々のRunnerを持つか、あるいは多くのセルに同じRunnerを登録することになるでしょう。
すべてのセルで競合しない一意のIDを保証するには?
このプロジェクトでは、少なくともネームスペースとプロジェクトがすべてのセルで一意のIDを持つことを想定しています。これらのテーブルは異なるデータベースにまたがっているため、一意のIDを保証するには新しいソリューションが必要になります。一意なIDが必要なテーブルは他にもあるでしょうし、GraphQLや他のAPIのルーティングをどのように解決するか、また他の設計目標によっては、主キーがすべてのテーブルで一意であることが望ましいと判断されるかもしれません。