Geo(開発者)

GeoはGitLabインスタンス同士を接続します。 一つのGitLabインスタンスはプライマリノードとして指定され、複数のセカンダリノードと一緒に実行することができます。 Geoは下図で見ることができる非常に多くのコンポーネントをオーケストレーションします。

Geo Architecture Diagram

レプリケーション層

Geoは異なるコンポーネントのレプリケーションを処理します:

セカンダリノードでは、データベースのレプリケーションを除いて、すべてがGeoLog Cursorによって調整されます。

Geo Log Cursor デーモン

Geo Log Cursor デーモンは、各セカンダリノード上で実行される個別のプロセスです。 Geoイベントログに新しいイベントがないか監視し、特定のイベントタイプごとにバックグラウンドジョブを作成します。

例えば、リポジトリが更新されると、Geoプライマリノードはリポジトリ更新イベントと関連付けられた Geo イベントを作成します。 Geo Log Cursor デーモンはこのイベントをピックアップし、Geo::RepositorySyncServiceGeo::WikiSyncService クラスを使ってリポジトリと Wiki をそれぞれ更新するGeo::ProjectSyncWorker ジョブをスケジュールします。

Geo Log Cursor デーモンは自動的に高可用性モードでオペレーションすることができます。 デーモンは時々ロックを取得しようとし、一度取得するとアクティブデーモンとして動作します。

同じノード上で実行中の他のデーモンはスタンバイ・モードになり、アクティブ・デーモンがロックを解除した場合に作業を再開できるようになります。

私たちは、ExclusiveLease 、プーリング・サイクルごとに更新される、小さなTTLを持つロック・タイプを使用しています。 これにより、タイムアウトを伴うグローバル・ロックを実装することができます。

プーリングサイクルの終了時に、デーモンがロックを更新または再取得できない場合、スタンバイモードに切り替わります。

データベースのレプリケーション

Geoはストリーミングレプリケーションを使用して、プライマリノードから セカンダリノードへデータベースを複製します。 このレプリケーションにより、セカンダリノードはデータベースに保存されたすべてのデータにアクセスできるようになります。 そのため、ユーザーはセカンダリノードにログインして、セカンダリノード上のすべてのイシューやマージリクエストなどを読むことができます。

リポジトリの複製

Geoはリポジトリの複製も行います。各セカンダリノードは追跡データベース内の各リポジトリの状態を追跡します。

リポジトリがレプリケートされる方法はいくつかあります:

プロジェクトレジストリ

Geo::ProjectRegistry クラスは、リポジトリの複製状態を追跡するためのモデルを定義します。 メインデータベースの各プロジェクトに対して、追跡データベースのレコードが 1 つ保持されます。

リポジトリについては以下のように記録されています:

  • 最後に同期したとき。
  • 最後に同期に成功した時間。
  • 再同期が必要な場合
  • 再試行を試みるべきとき。
  • 再試行回数。
  • もし検証されたのなら、そしていつ検証されたのか

また、プロジェクトWikiの属性を専用のカラムに保存します。

リポジトリ同期ワーカー

Geo::RepositorySyncWorker クラスはバックグラウンドで定期的に実行され、更新が必要なプロジェクトをGeo::ProjectRegistry モデルから検索します:

  • 未同期:セカンダリノードで同期されたことがなく、まだ存在していないプロジェクト。
  • Updated recently:Geo::ProjectRegistry モデルのlast_repository_successful_sync_atタイムスタンプよりも新しいlast_repository_updated_atタイムスタンプを持つプロジェクト。
  • 手動: 管理者は Geo管理パネルで手動でリポジトリに再同期のフラグを立てることができます。

セカンダリRETRIES_BEFORE_REDOWNLOADでリポジトリの取得に失敗した場合、Geo はいわゆる_再ダウンロードを_行います。ストレージのルートにある@geo-temporary ディレクトリにクリーンなクローンを作成します。クローンが成功したら、メインのリポジトリを新しくクローンされたリポジトリに置き換えます。

レプリケーションのアップロード

ファイルのアップロードもセカンダリノードにレプリケートされます。 同期の状態を追跡するために、Geo::UploadRegistry モデルが使用されます。

レジストリのアップロード

プロジェクトレジストリ同様、同期アップロードを追跡するGeo::UploadRegistry モデルがあります。

CI ジョブ アーティファクトと LFS オブジェクトはアップロードと同様の方法で同期されますが、それぞれGeo::JobArtifactRegistryGeo::LfsObjectRegistryモデルで追跡されます。

ファイルダウンロード 派遣労働者

リポジトリ同期ワーカーと同様に、Geo::FileDownloadDispatchWorker クラスがあり、セカンダリノードにまだ同期されていないすべてのアップロードを同期するために定期的に実行されます。

ファイルはHTTP(s)経由でコピーされ、/api/v4/geo/transfers/lfs/123などの/api/v4/geo/transfers/:type/:id エンドポイント経由で開始されます。

認証

ファイル転送を認証するため、GeoNode の各レコードには2つのフィールドがあります:

  • 公開アクセスキー (access_key フィールド)。
  • 秘密のアクセスキー (secret_access_key フィールド)。

セカンダリノードは、JWTリクエストによって自分自身を認証します。セカンダリノードがファイルをダウンロードしたい場合、Authorization ヘッダーを付けてHTTPリクエストを送信します:

Authorization: GL-Geo <access_key>:<JWT payload>

プライマリノードはaccess_key フィールドを使用して対応するセカンダリノードを検索し、JWT ペイロードを復号化します。この JWT ペイロードには、ファイルリクエストを識別するための追加情報が含まれています。 これにより、セカンダリノードは正しいデータベース ID に対して正しいファイルをダウンロードできるようになります。 例えば、LFS オブジェクトの場合、リクエストにはファイルの SHA256 和も含まれている必要があります。 JWT ペイロードの例は次のようになります:

{ "data": { sha256: "31806bb23580caab78040f8c45d329f5016b0115" }, iat: "1234567890" }

要求されたファイルが要求されたSHA256の合計と一致する場合、Geoプライマリノードは X-Sendfile機能を使ってデータを送信し、NGINXはRailsやWorkhorseに負荷をかけることなくファイル転送を処理します。

注:JWTは、関係するマシン間のクロックが同期している必要があります。そうでない場合、暗号化エラーで失敗する可能性があります。

GeoセカンダリへのGitプッシュ

Git Push Proxy はgitlab-shell コンポーネントの内部に組み込まれた機能として存在します。セカンダリノードでのみアクティビティを行います。セカンダリノードからリポジトリをクローンしたユーザーが、同じ URL にプッシュできるようにします。

セカンダリノードに向けられたGitpush リクエストはプライマリノードに送られ、pull リクエストはセカンダリノードで引き続き処理され、最大限の効率を発揮します。

HTTPS と SSH リクエストの処理は異なります:

  • HTTPS では、プライマリノードのプロジェクトを指すHTTP 302 Redirect をユーザーに渡します。Git クライアントは賢明なので、このステータスコードを理解してリダイレクトを処理します。
  • SSH では、リダイレクトする同等の方法がないので、リクエストをプロキシする必要があります。 これはgitlab-shell内部で行われ、まずリクエストを HTTP プロトコルに変換し、プライマリノードにプロキシします。

gitlab-shell デーモンは、/api/v4/allowedからのレスポンスに基づいて、プロキシするタイミングを認識します。特別なHTTP 300 ステータスコードが返され、レスポンスボディで指定された「カスタムアクション」を実行します。 レスポンスには、プロキシされたpush オペレーションをプライマリノードで実行するための追加データが含まれています。

トラッキングデータベースの使用

Geoセカンダリノードには、レプリケートされるメインデータベースとともに、独自の独立したトラッキングデータベースがあります。

トラッキングデータベースにはセカンダリノードの状態が含まれています。

アップグレードの一環として実行する必要があるデータベース移行は、各セカンダリノード上のトラッキングデータベースに適用する必要があります。

設定

config/database_geo.ymlee/db/geoディレクトリには、このデータベースのスキーマとマイグレーションがあります。

データベースのマイグレーションを記述するには、GeoMigrationGenerator

rails g geo_migration [args] [options]

追跡データベースを移行するには

bundle exec rake geo:db:migrate

外部データ・ラッパー

GitLab 10.1で導入されました。

Foreign Data Wrapper(FDW)はGeoLog Cursorで使用され、多くの同期オペレーションのパフォーマンスを向上させます。

FDWはPostgreSQLの拡張機能(postgres_fdw)で、Geo Tracking Database(セカンダリノー)内部で有効になっており、読み取り専用のデータベースレプリカに接続し、両方のインスタンスからクエリやデータのフィルタリングを実行することができます。

この永続的な接続は、gitlab_secondary. gitlab_secondaryGitLabという名前のFDWサーバーとして設定されます。gitlab_secondaryこの設定は、データベースのユーザーコンテキスト内にのみ存在 gitlab_secondaryします。 GitLabが.gitlab_secondaryGitLabにアクセスするには gitlab_secondary、以前に設定したのと同じデータベースユーザーを使う必要があります。

Geo Tracking Database は、独自の制限によって制限された通常のユーザーとして、FDW 経由で読み取り専用のデータベースレプリカにアクセスします。認証情報は、以前にマップされたSERVER (gitlab_secondary) に関連付けられたUSER MAPPING として設定されます。

FDWの設定と認証情報の定義は、Omnibus GitLabgitlab-ctl reconfigure コマンドによって自動的に管理されます。

外部テーブルのリフレッシュ

新しい Geo ノードが構成されるたびに、またはプライマリノードでデータベーススキーマが変更されるたびに、以下を実行してセカンダリノードの外部テーブルをリフレッシュする必要があります:

bundle exec rake geo:db:refresh_foreign_tables

これを行わないと、セカンダリノードが正しく機能しなくなります。セカンダリノードは以下のPostgreSQLエラーのようなエラーメッセージを生成します:

ERROR:  relation "gitlab_secondary.ci_job_artifacts" does not exist at character 323
STATEMENT:                SELECT a.attname, format_type(a.atttypid, a.atttypmod),
                          pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
                     FROM pg_attribute a LEFT JOIN pg_attrdef d
                       ON a.attrelid = d.adrelid AND a.attnum = d.adnum
                    WHERE a.attrelid = '"gitlab_secondary"."ci_job_artifacts"'::regclass
                      AND a.attnum > 0 AND NOT a.attisdropped
                    ORDER BY a.attnum

外部テーブルからのデータアクセス

SQLレベルでは、gitlab_secondary.*のデータをSELECT

Geo Tracking DatabaseのFDWから全プロジェクトにアクセスする方法の例です:

SELECT * FROM gitlab_secondary.projects;

実際の例として、Tracking Databaseでアーカイブされていないプロジェクトをフィルタリングする方法を示します:

SELECT project_registry.*
  FROM project_registry
  JOIN gitlab_secondary.projects
    ON (project_registry.project_id = gitlab_secondary.projects.id
   AND gitlab_secondary.projects.archived IS FALSE)

ActiveRecordレベルでは、外部テーブルを表すモデルを追加しています。 これらは少し異なる方法でマッピングする必要があり、読み取り専用です。

ee/app/models/geo/fdw 、既存のFDWモデルを参照してください。

開発者の視点からは、データベースビューを表すモデルを作成するのと変わりません。

上記の例では、次のようにしてプロジェクトにアクセスできます:

Geo::Fdw::Project.all

また、未アーカイブのプロジェクトでフィルタリングしてProjectRegistry にアクセスすることもできます:

# We have to use Arel here:
project_registry_table = Geo::ProjectRegistry.arel_table
fdw_project_table = Geo::Fdw::Project.arel_table

project_registry_table.join(fdw_project_table)
                      .on(project_registry_table[:project_id].eq(fdw_project_table[:id]))
                      .where((fdw_project_table[:archived]).eq(true)) # if you append `.to_sql` you can check generated query

ファインダー

Geoはファインダーを使用しています。ファインダーは、トラッキングデータベースとメインデータベースでプロジェクトや添付ファイルなどを探すという重労働を引き受けてくれるクラスです。

ファインダーズ・パフォーマンス

例えば、同期プロジェクトの数をカウントするには、通常、一方のデータベースからプロジェクト ID を取得し、もう一方のデータベースでその状態をチェックする必要があります。 これには時間がかかり、多くのメモリを必要とします。

これを克服するために、ファインダーはFDW(Foreign Data Wrappers)を使用しています。 これにより、メイン・データベースとトラッキング・データベースの間で定期的にJOIN

Redis

セカンダリノードのRedisはプライマリノードと同じように動作し、キャッシュ、セッションの保存、その他の永続的なデータの保存に使用されます。

プライマリノードとセカンダリノード間のRedisデータレプリケーションは使用しないため、セッションなどはノード間で共有されません。

オブジェクトストレージ

GitLabはオプションでオブジェクトストレージを使い、ディスクに保存するデータを保存することができます。 このようなことが可能です:

  • LFSオブジェクト
  • CI ジョブのアーティファクト
  • アップロードファイル

オブジェクトストレージに保存されているオブジェクトは、Geo では扱えません。 Geo はオブジェクトストレージのアイテムを無視します:

  • オブジェクトストレージレイヤーは、それ自身の地理的なレプリケーションの世話をする必要があります。
  • すべてのセカンダリノードは同じストレージノードを使用する必要があります。

検証

リポジトリの検証

リポジトリはチェックサムで検証されます。

プライマリノードはリポジトリのチェックサムを計算します。 基本的にはすべての Git 参照をハッシュし、そのハッシュをデータベースのproject_repository_states テーブルに保存します。

セカンダリノードも同様にクローンのハッシュを計算し、プライマリノードが計算した値とハッシュを比較します。 不一致がある場合、Geo はこれを不一致としてマークし、管理者は Geo管理パネルでこれを見ることができます。

用語集

一次ノード

プライマリノードとは、Geo セットアップの中で読み書き可能な単一のノードのことです。 これは真実の単一のソースであり、Geoセカンダリノードはそこからデータを複製します。

Geoセットアップでは、プライマリノードは1つだけで、すべてのセカンダリノードはそのプライマリノードに接続します。

セカンダリノード

セカンダリノードは、地理的に異なる場所で稼働するプライマリノードの読み取り専用のレプリカです。

ストリーミング・レプリケーション

GeoはPostgreSQLのストリーミングレプリケーション機能に依存しており、データベースデータとデータベーススキーマを完全に複製します。 データベースレプリカは読み取り専用のコピーです。

ストリーミング・レプリケーションは、Write Ahead Logs(WAL)に依存します。 これらのログはレプリカにコピーされ、そこで再生されます。

ストリーミング・レプリケーションではスキーマも複製されるため、データベースの移行をセカンダリノードで実行する必要はありません。

追跡データベース

各 Geoセカンダリノード上のデータベースで、そのデータベースが存在するノードの状態を保持します。 詳しくはTracking データベースの使用を参照してください。

エフディーダブリュー

外部データラッパ(FDW)はPostgreSQLに組み込まれている機能で、異なるデータソースからデータをクエリすることができます。 Geoでは、異なるPostgreSQLインスタンスからデータをクエリするために使用されます。

Geo イベントログ

Geoprimaryは、geo_event_log テーブルにイベントを保存します。ログの各エントリには、特定のタイプのイベントが含まれます。 これらのタイプのイベントには、以下のものが含まれます:

  • リポジトリ削除イベント
  • リポジトリ名称変更イベント
  • リポジトリ変更イベント
  • リポジトリ作成イベント
  • ハッシュストレージ移行イベント
  • Lfs オブジェクト削除イベント
  • ハッシュドストレージ・アタッチイベント
  • ジョブ・アーティファクト削除イベント
  • 削除イベントのアップロード

Geo ログカーソル

セカンダリノードで実行され、Geo::EventLog の新しい行を探すプロセス。

コードの特徴

Gitlab::Geo 公共施設

Geoに関連する小さなユーティリティメソッドは、ee/lib/gitlab/geo.rbファイルに入ります。

これらのメソッドの多くは、RequestStore クラスを使用してキャッシュされ、コードベース全体でメソッドを使用する際のパフォーマンスへの影響を軽減します。

現在のノード

クラスメソッド.current_node は、現在のノードのGeoNode レコードを返します。

gitlab.yml からhostportrelative_url_root の値を使用し、データベース内を検索してどのノードにいるかを特定します(GeoNode.current_nodeを参照)。

一次または二次

現在のノードがプライマリ・ノードかセカンダリ・ノードかを判断するには、.primary? および.secondary? クラス・メソッドを使用します。

ノードが有効化されていない場合、これらのメソッドは両方ともノード上でfalse を返すことが可能です。有効化」を参照してください。

Geo データベースは設定されていますか?

また、初期化時に発生する問題に対処する際に、追加の問題があります。いくつかの場所では、Gitlab::Geo.geo_database_configured? メソッドを使って、セカンダリノード上にしか存在しないトラッキングデータベースがノードにあるかどうかをチェックしています。 これは、新しいノードのブートストラップ中に発生する可能性のあるレースコンディションを克服するものです。

イネーブルメント

Geo 機能が有効なのは、その機能が含まれた有効なライセンスがユーザーにあり、Geo Nodes 画面で少なくとも 1 つのノードが定義されている場合です。

Gitlab::Geo.enabled?Gitlab::Geo.license_allows? メソッドを参照。

読み取り専用

Geoセカンダリノードはすべて読み取り専用です。

読み取り専用データベースの一般原則は、すべてのセカンダリノードに適用されます。そのため、Gitlab::Database.read_only? メソッドは、セカンダリノードでは常にtrue を返します。

ノードがセカンダリノードであるためにいくつかの書き込みアクションが許可されない場合、Gitlab::Geo.secondary?の代わりにGitlab::Database.read_only? またはGitlab::Database.read_write? ガードを追加することを検討してください。

レプリケートされた設定では、データベース自体はすでに読み取り専用になっているので、そのために余計な手順を踏む必要はありません。

新しいデータ型を複製するために必要な手順

GitLabが進化するにつれて、Geoレプリケーションシステムに新しいリソースを常に追加する必要があります。 実装はリソースの仕様によって異なりますが、注意しなければならないことがいくつかあります:

  • プライマリ・サイトでのイベント生成。 新しいリソースが変更/更新されるたびに、ログ・カーソル用のタスクを作成する必要があります。
  • イベント処理:ログ・カーソルは、プライマリ・サイトによって生成されるすべてのイベント・タイプのハンドラを持つ必要があります。
  • ワーカー(cronジョブ)を派遣します。 バックフィル条件がうまく機能していることを確認します。
  • 同期ワーカー。
  • すべての可能な状態を持つレジストリ。
  • 検証。
  • セカンダリサイトの同期設定を変更すると、一部のリソースをクリーンアップする必要があります。
  • Geo Node Status. GitLab Admin Areaでのプレゼンテーションと同様に、APIエンドポイントを提供する必要があります。
  • rake gitlab:geo:check コマンドも更新する必要があります。

Geoセルフサービス・フレームワーク(アルファ版)

新しいGeoセルフサービス・フレームワーク(アルファ版)の開発に着手し、新しいデータタイプの追加がとても簡単になりました。

通信チャネルの歴史

コミュニケーション・チャンネルは最初の反復から変化しています。

カスタムコード (GitLab 8.6 以前)

GitLab 8.6以前のバージョンでは、プライマリノードからセカンダリノードへのHTTPリクエストによる通知を処理するためにカスタムコードが使用されています。

システムフック (GitLab 8.7 から 9.5)

その後、カスタム・コードから脱却し、システム・フックの使用を開始することが決定されました。 より多くの人々がシステム・フックを使用していたため、この通信レイヤーの改善によって多くの人が恩恵を受けることになります。

私たちのAPIコード(Grape)には、このSystem Hooksからのすべてのリクエストを受け取る特定の内部エンドポイントがあります:/api/v4/geo/receive_events

各イベントをevent_name フィールドで切り替えてフィルタリングします。

Geoログカーソル(GitLab 10.0以上)

GitLab 10.0 以降、System Webhooksは使われなくなり、代わりに Geo Log Cursor が使われるようになりました。Log Cursor はGeo::EventLog の行を走査して、前回ログをチェックしたときからの変更があるかどうかを確認し、リポジトリの更新、削除、変更、名前の変更を処理します。

これは、レプリケートされたデータベース内にテーブルが存在するため、従来の方法と比較して2つの利点があります:

  • レプリケーションは同期的で、イベントの順序を保持します。
  • イベントのレプリケーションは、データベースの変更と同時に発生します。

セルフサービスの枠組み

作業中のリソースのGeoレプリケーションを簡単に追加したい場合は、セルフサービスフレームワークをご覧ください。