- レプリケーション層
- 認証
- GeoセカンダリへのGitプッシュ
- トラッキングデータベースの使用
- ファインダー
- Redis
- オブジェクトストレージ
- 検証
- 用語集
- Geo イベントログ
- コードの特徴
- 新しいデータ型を複製するために必要な手順
- 通信チャネルの歴史
- セルフサービスの枠組み
Geo(開発者)
GeoはGitLabインスタンス同士を接続します。 一つのGitLabインスタンスはプライマリノードとして指定され、複数のセカンダリノードと一緒に実行することができます。 Geoは下図で見ることができる非常に多くのコンポーネントをオーケストレーションします。
レプリケーション層
Geoは異なるコンポーネントのレプリケーションを処理します:
- データベース: キャッシュとジョブを除くアプリケーション全体を含みます。
- Gitリポジトリ:プロジェクトとWikiの両方を含みます。
- アップロードされたブロブ:イシューに添付された画像から、CIからの生のログやアセットまで、あらゆるものが含まれます。
セカンダリノードでは、データベースのレプリケーションを除いて、すべてがGeoLog Cursorによって調整されます。
Geo Log Cursor デーモン
Geo Log Cursor デーモンは、各セカンダリノード上で実行される個別のプロセスです。 Geoイベントログに新しいイベントがないか監視し、特定のイベントタイプごとにバックグラウンドジョブを作成します。
例えば、リポジトリが更新されると、Geoプライマリノードはリポジトリ更新イベントと関連付けられた Geo イベントを作成します。 Geo Log Cursor デーモンはこのイベントをピックアップし、Geo::RepositorySyncService
とGeo::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::JobArtifactRegistry
とGeo::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に負荷をかけることなくファイル転送を処理します。
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.yml
ee/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_secondary
GitLabという名前のFDWサーバーとして設定されます。gitlab_secondary
この設定は、データベースのユーザーコンテキスト内にのみ存在 gitlab_secondary
します。 GitLabが.gitlab_secondary
GitLabにアクセスするには 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
からhost
、port
、relative_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レプリケーションを簡単に追加したい場合は、セルフサービスフレームワークをご覧ください。