Geoセルフサービス・フレームワーク(アルファ版)
#g_geo
に連絡するか、イシューまたはマージリクエストに@geo-team
と記載してください。Geo は、Geo ノード間でデータタイプを簡単に複製できる API を提供します。この API は Ruby Domain-Specific Language(DSL) として提供され、データタイプを作成したエンジニアの労力を最小限に抑えてデータを複製できるようにすることを目的としています。
命名法
APIを使いこなす前に、開発者はGeo特有の命名規則を知っておく必要があります。
モデル : モデルとはActive Modelのことで、Railsのコードベース全体ではこう呼ばれています。 通常はデータベースのテーブルに関連付けられています。 Geo的には、モデルは1つ以上のリソースを持つことができます。
リソース : リソースはモデルに属し、GitLab機能によって生成されるデータの一部です。 ストレージメカニズムを使って永続化されます。 デフォルトでは、リソースは複製可能ではありません。
データ型 : データ型はリソースの保存方法です。 各リソースは Geo がサポートしているデータ型のどれかに収まる必要があります: :- Git リポジトリ :- Blob :- データベース : 詳しくはデータ型をご覧ください。
Geo Replicable : レプリカブルとは、GeoがGeoノード間で同期させたいリソースのことです。 レプリカブルのサポートされるデータタイプは限られています。 既知のデータタイプに属するリソースのレプリケーションを実装するのに必要な労力は最小限です。
Geo Replicator : Geo Replicator は、複製可能なものを複製する方法を知っているオブジェクトです。 Geo Replicator の役割は、:- イベントの発射(producer):- イベントの消費(consumer):Geo Replicable データ型に結びつきます。 すべての Replicator は、イベントの処理(つまり、生成と消費)に使用できる共通のインターフェースを持っています。 Geo Replicator は、プライマリノード(イベントが生成される場所)とセカンダリノード(イベントが消費される場所)の間の通信を引き受けます。 Geo を機能に組み込みたいエンジニアは、これを実現するために Replicator の API を使用します。
Geo Domain-Specific Language:どのリソースをどのように複製するかをエンジニアが簡単に指定できるようにする構文上の糖分。
Geo ドメイン固有言語
レプリケーター
まず最初に、レプリケータを記述する必要があります。レプリケータはee/app/replicators/geo
にあります。複数のリソースが同じモデルに紐付いている場合でも、レプリケートする必要があるリソースごとに、個別のレプリケータを指定する必要があります。
例えば、次のレプリケーターはパッケージファイルをレプリケートします:
module Geo
class PackageFileReplicator < Gitlab::Geo::Replicator
# Include one of the strategies your resource needs
include ::Geo::BlobReplicatorStrategy
# Specify the CarrierWave uploader needed by the used strategy
def carrierwave_uploader
model_record.file
end
# Specify the model this replicator belongs to
def self.model
::Packages::PackageFile
end
end
end
クラス名は一意でなければなりません。 また、レジストリのテーブル名と密接に結びつきますので、この例ではレジストリテーブルをpackage_file_registry
とします。
Geoがサポートするデータタイプには、それぞれ異なる戦略があります。 あなたのニーズに合ったものを選んでください。
モデルへのリンク
このレプリケーターをモデルに結びつけるには、モデルコードに次のように追加する必要があります:
class Packages::PackageFile < ApplicationRecord
include ::Gitlab::Geo::ReplicableModel
with_replicator Geo::PackageFileReplicator
end
API
これが設置されていれば、模型を通してレプリケーターにアクセスするのは簡単です:
package_file = Packages::PackageFile.find(4) # just a random id as example
replicator = package_file.replicator
あるいはレプリケーターからモデルを取り戻すか:
replicator.model_record
=> <Packages::PackageFile id:4>
レプリケータは、ActiveRecordのフックなどでイベントを生成するために使用できます:
after_create_commit -> { replicator.publish_created_event }
図書館
すべてのフレームワークはee/lib/gitlab/geo/
にあります。
既存のレプリケーター戦略
新しい種類のレプリケーター・ストラテジーを書く前に、あなたのリソースが既存のストラテジーのどれかですでに処理できるかどうかを以下で確認してください。 不明な場合は、Geoチームに相談してください。
ブロブ・レプリケーター戦略
CarrierWaveの Uploader::Base
を使用するモデルは、GeoのGeo::BlobReplicatorStrategy
モジュールで簡単にサポートできます。
Geo では、すべてのファイルをファーストクラス市民として扱うことを強く推奨しています。
例えば、widgets
テーブルを持つWidget
モデルによって参照されるファイルのサポートを追加するには、以下の手順を実行します:
レプリケーション
-
Widget
クラスにGitlab::Geo::ReplicableModel
を含め、レプリケーター・クラスwith_replicator Geo::WidgetReplicator
を指定します。この時点で、
Widget
クラスは次のようになっているはずです:# frozen_string_literal: true class Widget < ApplicationRecord include ::Gitlab::Geo::ReplicableModel with_replicator Geo::WidgetReplicator mount_uploader :file, WidgetUploader def self.replicables_for_geo_node # Should be implemented. The idea of the method is to restrict # the set of synced items depending on synchronization settings end ... end
-
ee/app/replicators/geo/widget_replicator.rb
を作成します。CarrierWave::Uploader
を返す#carrierwave_uploader
メソッドを実装します。Widget
クラスを返す.model
クラスメソッドを実装します。# frozen_string_literal: true module Geo class WidgetReplicator < Gitlab::Geo::Replicator include ::Geo::BlobReplicatorStrategy def self.model ::Widget end def carrierwave_uploader model_record.file end end end
-
ee/spec/replicators/geo/widget_replicator_spec.rb
を作成し、共有サンプル用の変数model_record
を定義するために必要な設定を行います。# frozen_string_literal: true require 'spec_helper' RSpec.describe Geo::WidgetReplicator do let(:model_record) { build(:widget) } it_behaves_like 'a blob replicator' end
-
Geo セカンダリが各 Widget ファイルの同期と検証状態を追跡できるように、
widget_registry
テーブルを作成します:# frozen_string_literal: true class CreateWidgetRegistry < ActiveRecord::Migration[6.0] DOWNTIME = false disable_ddl_transaction! def up unless table_exists?(:widget_registry) ActiveRecord::Base.transaction do create_table :widget_registry, id: :bigserial, force: :cascade do |t| t.integer :widget_id, null: false t.integer :state, default: 0, null: false, limit: 2 t.integer :retry_count, default: 0, limit: 2 t.text :last_sync_failure t.datetime_with_timezone :retry_at t.datetime_with_timezone :last_synced_at t.datetime_with_timezone :created_at, null: false t.index :widget_id t.index :retry_at t.index :state end end end add_text_limit :widget_registry, :last_sync_failure, 255 end def down drop_table :widget_registry end end
-
ee/app/models/geo/widget_registry.rb
を作成します:# frozen_string_literal: true class Geo::WidgetRegistry < Geo::BaseRegistry include Geo::ReplicableRegistry MODEL_CLASS = ::Widget MODEL_FOREIGN_KEY = :widget_id belongs_to :widget, class_name: 'Widget' end
メソッド
has_create_events?
は、ほとんどの場合true
を返すはずです。しかし、追加するエンティティに create イベントがない場合は、メソッドを追加しないでください。 -
ee/app/workers/geo/secondary/registry_consistency_worker.rb
のREGISTRY_CLASSES
を更新。 -
ee/spec/factories/geo/widget_registry.rb
を作成します:# frozen_string_literal: true FactoryBot.define do factory :geo_widget_registry, class: 'Geo::WidgetRegistry' do widget state { Geo::WidgetRegistry.state_value(:pending) } trait :synced do state { Geo::WidgetRegistry.state_value(:synced) } last_synced_at { 5.days.ago } end trait :failed do state { Geo::WidgetRegistry.state_value(:failed) } last_synced_at { 1.day.ago } retry_count { 2 } last_sync_failure { 'Random error' } end trait :started do state { Geo::WidgetRegistry.state_value(:started) } last_synced_at { 1.day.ago } retry_count { 0 } end end end
-
ee/spec/models/geo/widget_registry_spec.rb
を作成します:# frozen_string_literal: true require 'spec_helper' RSpec.describe Geo::WidgetRegistry, :geo, type: :model do let_it_be(:registry) { create(:geo_widget_registry) } specify 'factory is valid' do expect(registry).to be_valid end include_examples 'a Geo framework registry' describe '.find_registry_differences' do ... # To be implemented end end
ウィジェットは Geo によって複製されるはずです!
検証
-
Geo プライマリが検証状態を追跡できるように、
widgets
テーブルに検証状態フィールドを追加します:# frozen_string_literal: true class AddVerificationStateToWidgets < ActiveRecord::Migration[6.0] DOWNTIME = false def change add_column :widgets, :verification_retry_at, :datetime_with_timezone add_column :widgets, :verified_at, :datetime_with_timezone add_column :widgets, :verification_checksum, :binary, using: 'verification_checksum::bytea' add_column :widgets, :verification_failure, :string add_column :widgets, :verification_retry_count, :integer end end
-
verification_failure
とverification_checksum
に部分インデックスを追加し、再検証を効率的に行えるようにします:# frozen_string_literal: true class AddVerificationFailureIndexToWidgets < ActiveRecord::Migration[6.0] include Gitlab::Database::MigrationHelpers DOWNTIME = false disable_ddl_transaction! def up add_concurrent_index :widgets, :verification_failure, where: "(verification_failure IS NOT NULL)", name: "widgets_verification_failure_partial" add_concurrent_index :widgets, :verification_checksum, where: "(verification_checksum IS NOT NULL)", name: "widgets_verification_checksum_partial" end def down remove_concurrent_index :widgets, :verification_failure remove_concurrent_index :widgets, :verification_checksum end end
Geo:セルフサービスフレームワーク - パッケージファイル検証のための最初の実装」の一部として行われるべきです。
ウィジェットは Geo によって検証されるはずです!
メトリクス
メトリクスはGeo::MetricsUpdateWorker
によって収集され、UI に表示するためにGeoNodeStatus
に保存され、Prometheus に送信されます。
-
ee/app/models/geo_node_status.rb
の配列GeoNodeStatus#RESOURCE_STATUS_FIELDS
にフィールドwidget_count
,widget_checksummed_count
,widget_checksum_failed_count
,widget_synced_count
,widget_failed_count
,widget_registry_count
を追加。 -
ee/app/models/geo_node_status.rb
のGeoNodeStatus#PROMETHEUS_METRICS
ハッシュに同じフィールドを追加します。 -
doc/administration/monitoring/prometheus/gitlab_metrics.md
のSidekiq metrics
テーブルに同じフィールドを追加します。 -
doc/api/geo_nodes.md
のGET /geo_nodes/status
レスポンス例に同じフィールドを追加します。 -
ee/spec/models/geo_node_status_spec.rb
とee/spec/factories/geo_node_statuses.rb
に同じフィールドを追加。 -
GeoNodeStatus#load_data_from_current_node
でwidget_count
を設定します:self.widget_count = Geo::WidgetReplicator.primary_total_count
-
widget_synced_count
,widget_failed_count
,widget_registry_count
を設定するためにGeoNodeStatus#load_widgets_data
を追加します:def load_widget_data self.widget_synced_count = Geo::WidgetReplicator.synced_count self.widget_failed_count = Geo::WidgetReplicator.failed_count self.widget_registry_count = Geo::WidgetReplicator.registry_count end
-
GeoNodeStatus#load_secondary_data
のGeoNodeStatus#load_widgets_data
にお電話ください。 -
GeoNodeStatus#load_verification_data
にwidget_checksummed_count
とwidget_checksum_failed_count
を設定します:self.widget_checksummed_count = Geo::WidgetReplicator.checksummed_count self.widget_checksum_failed_count = Geo::WidgetReplicator.checksum_failed_count
ウィジェットのレプリケーションと検証メトリクスが、API、管理エリアUI、Prometheusで利用できるようになりました!
GraphQL API
-
ee/app/graphql/types/geo/geo_node_type.rb
のGeoNodeType
に新しいフィールドを追加します:field :widget_registries, ::Types::Geo::WidgetRegistryType.connection_type, null: true, resolver: ::Resolvers::Geo::WidgetRegistriesResolver, description: 'Find widget registries on this Geo node', feature_flag: :geo_self_service_framework
-
ee/spec/graphql/types/geo/geo_node_type_spec.rb
のexpected_fields
配列に、新しいフィールド名widget_registries
を追加します。 -
ee/app/graphql/resolvers/geo/widget_registries_resolver.rb
を作成します:# frozen_string_literal: true module Resolvers module Geo class WidgetRegistriesResolver < BaseResolver include RegistriesResolver end end end
-
ee/spec/graphql/resolvers/geo/widget_registries_resolver_spec.rb
を作成します:# frozen_string_literal: true require 'spec_helper' RSpec.describe Resolvers::Geo::WidgetRegistriesResolver do it_behaves_like 'a Geo registries resolver', :geo_widget_registry end
-
ee/app/finders/geo/widget_registry_finder.rb
を作成します:# frozen_string_literal: true module Geo class WidgetRegistryFinder include FrameworkRegistryFinder end end
-
ee/spec/finders/geo/widget_registry_finder_spec.rb
を作成します:# frozen_string_literal: true require 'spec_helper' RSpec.describe Geo::WidgetRegistryFinder do it_behaves_like 'a framework registry finder', :geo_widget_registry end
-
ee/app/graphql/types/geo/widget_registry_type.rb
を作成します:# frozen_string_literal: true module Types module Geo # rubocop:disable Graphql/AuthorizeTypes because it is included class WidgetRegistryType < BaseObject include ::Types::Geo::RegistryType graphql_name 'WidgetRegistry' description 'Represents the sync and verification state of a widget' field :widget_id, GraphQL::ID_TYPE, null: false, description: 'ID of the Widget' end end end
-
ee/spec/graphql/types/geo/widget_registry_type_spec.rb
を作成します:# frozen_string_literal: true require 'spec_helper' RSpec.describe GitlabSchema.types['WidgetRegistry'] do it_behaves_like 'a Geo registry type' it 'has the expected fields (other than those included in RegistryType)' do expected_fields = %i[widget_id] expect(described_class).to have_graphql_fields(*expected_fields).at_least end end
-
ee/spec/requests/api/graphql/geo/registries_spec.rb
の以下の共有例を複製および修正することで、GraphQL APIを介してフロントエンドにWidgetレジストリデータを提供するためのインテグレーションテストを追加します:it_behaves_like 'gets registries for', { field_name: 'widgetRegistries', registry_class_name: 'WidgetRegistry', registry_factory: :geo_widget_registry, registry_foreign_key_field_name: 'widgetId' }
個々のウィジェットの同期と検証データは、GraphQL APIを介して利用できるようになりました!
- update “イベントの複製に注意してください。 Geo Framework は現在、”update “イベントの複製をサポートしていません。なぜなら、現時点では、フレームワークに追加されたすべてのエンティティは不変だからです。 もし、あなたが追加しようとしているエンティティがそうであるなら、https://gitlab.com/gitlab-org/gitlab/-/issues/118743とhttps://gitlab.com/gitlab-org/gitlab/-/issues/118745 を例として、新しいイベントタイプを追加してください。また、追加したら、この通知を削除してください。
管理者UI
To-Do:これはGeoの一部として行う必要があります:セルフサービスフレームワークのリプリケーブルのためのフロントエンドを実装します。
ウィジェットの同期および検証データ(集計および個別)は、管理者UIで利用できるようになりました!