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

注:このドキュメントは変更される可能性があります。 これは私たちが取り組んでいる提案であり、実装が完了したら、このドキュメントは更新されます。エピックで進捗状況を追ってください。
注:Geo セルフサービスフレームワークは現在アルファ版です。 新しいデータタイプをレプリケートする必要がある場合は、Geo チームに連絡してオプションについて相談してください。 Slack の#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 モデルによって参照されるファイルのサポートを追加するには、以下の手順を実行します:

レプリケーション

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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
    
  5. 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 イベントがない場合は、メソッドを追加しないでください。

  6. ee/app/workers/geo/secondary/registry_consistency_worker.rbREGISTRY_CLASSES を更新。

  7. 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
    
  8. 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 によって複製されるはずです!

検証

  1. 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
    
  2. verification_failureverification_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 に送信されます。

  1. 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 を追加。
  2. ee/app/models/geo_node_status.rbGeoNodeStatus#PROMETHEUS_METRICS ハッシュに同じフィールドを追加します。
  3. doc/administration/monitoring/prometheus/gitlab_metrics.mdSidekiq metrics テーブルに同じフィールドを追加します。
  4. doc/api/geo_nodes.mdGET /geo_nodes/status レスポンス例に同じフィールドを追加します。
  5. ee/spec/models/geo_node_status_spec.rbee/spec/factories/geo_node_statuses.rbに同じフィールドを追加。
  6. GeoNodeStatus#load_data_from_current_nodewidget_count を設定します:

    self.widget_count = Geo::WidgetReplicator.primary_total_count
    
  7. 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
    
  8. GeoNodeStatus#load_secondary_dataGeoNodeStatus#load_widgets_data にお電話ください。

  9. GeoNodeStatus#load_verification_datawidget_checksummed_countwidget_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

  1. ee/app/graphql/types/geo/geo_node_type.rbGeoNodeType に新しいフィールドを追加します:

    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
    
  2. ee/spec/graphql/types/geo/geo_node_type_spec.rbexpected_fields 配列に、新しいフィールド名widget_registries を追加します。

  3. 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
    
  4. 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
    
  5. ee/app/finders/geo/widget_registry_finder.rbを作成します:

    # frozen_string_literal: true
    
    module Geo
      class WidgetRegistryFinder
        include FrameworkRegistryFinder
      end
    end
    
  6. 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
    
  7. 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
    
  8. 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
    
  9. 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を介して利用できるようになりました!

  1. update “イベントの複製に注意してください。 Geo Framework は現在、”update “イベントの複製をサポートしていません。なぜなら、現時点では、フレームワークに追加されたすべてのエンティティは不変だからです。 もし、あなたが追加しようとしているエンティティがそうであるなら、https://gitlab.com/gitlab-org/gitlab/-/issues/118743https://gitlab.com/gitlab-org/gitlab/-/issues/118745 を例として、新しいイベントタイプを追加してください。また、追加したら、この通知を削除してください。

管理者UI

To-Do:これはGeoの一部として行う必要があります:セルフサービスフレームワークのリプリケーブルのためのフロントエンドを実装します。

ウィジェットの同期および検証データ(集計および個別)は、管理者UIで利用できるようになりました!