高度な検索マイグレーションスタイルガイド

高度な検索マイグレーションの新規作成

note
この機能はGitLab 13.0以降で作成されたインデックスにのみ対応しています。

スクリプト

GitLab 16.3 で導入されました

scripts/elastic-migration を実行し、プロンプトに従って作成してください:

  • マイグレーションを定義するマイグレーションファイル:ee/elastic/migrate/YYYYMMDDHHMMSS_migration_name.rb
  • マイグレーションをテストするための spec ファイル:ee/spec/elastic/migrate/YYYYMMDDHHMMSS_migration_name_spec.rb
  • マイグレーションを識別するための辞書ファイル:ee/elastic/docs/YYYYMMDDHHMMSS_migration_name.yml

手動

GitLab 13.6で導入されました

ee/elastic/migrate/ フォルダに、YYYYMMDDHHMMSS_migration_name.rb というファイル名で新しいファイルを作成します。この形式はRailsデータベースのマイグレーションでも同じです。

# frozen_string_literal: true

class MigrationName < Elastic::Migration
  # Important: Any updates to the Elastic index mappings must be replicated in the respective
  # configuration files:
  #   - `Elastic::Latest::Config`, for the main index.
  #   - `Elastic::Latest::<Type>Config`, for standalone indices.

  def migrate
  end

  # Check if the migration has completed
  # Return true if completed, otherwise return false
  def completed?
  end
end

適用されたマイグレーションはgitlab-#{RAILS_ENV}-migrations インデックスに保存されます。実行されなかったマイグレーションはすべて、Elastic::MigrationWorker cronワーカーによって順次適用されます。

Elasticインデックスのマッピングを更新するには、それぞれのファイルに設定を適用します:

  • メインインデックスにはElastic::Latest::Config.
  • スタンドアロン・インデックスの場合:Elastic::Latest::<Type>Config.

マイグレーションは再試行回数を制限して構築することができ、失敗して停止とマークすることができます。マイグレーションの再試行をサポートするために必要なデータやインデックスのクリーンアップは、マイグレーション内で処理する必要があります。

マイグレーションヘルパー

ee/app/workers/concerns/elastic/ では、以下のマイグレーションヘルパーが利用可能です:

Elastic::MigrationBackfillHelper

インデックス内の特定のフィールドを埋め戻します。ほとんどの場合、そのフィールドのマッピングはすでに追加されているはずです。

単一のフィールドを埋め戻すにはindex_name およびfield_name メソッドが必要です。

class MigrationName < Elastic::Migration
  include Elastic::MigrationBackfillHelper

  private

  def index_name
    Issue.__elasticsearch__.index_name
  end

  def field_name
    :schema_version
  end
end

いずれかのフィールドが NULL の場合に複数のフィールドを埋め戻すには、index_name およびfield_names メソッドが必要です。

class MigrationName < Elastic::Migration
  include Elastic::MigrationBackfillHelper

  private

  def index_name
    Issue.__elasticsearch__.index_name
  end

  def field_names
    %w[schema_version visibility_level]
  end
end

Elastic::MigrationUpdateMappingsHelper

マッピングを指定してput_mapping を呼び出すことで、インデックス内のマッピングを更新します。

index_name およびnew_mappings メソッドが必要です。

class MigrationName < Elastic::Migration
  include Elastic::MigrationUpdateMappingsHelper

  private

  def index_name
    Issue.__elasticsearch__.index_name
  end

  def new_mappings
    {
      schema_version: {
        type: 'short'
      }
    }
  end
end

Elastic::MigrationRemoveFieldsHelper

インデックスから指定したフィールドを削除します。

index_name,document_type メソッドが必要です。削除するフィールドが 1 つの場合はfield_to_remove メソッドを追加し、そうでない場合はフィールドの配列を指定してfields_to_remove を追加します。

document_type にマッチするドキュメントが Elasticsearch で指定されたフィールドを持っているかバッチでチェックします。ドキュメントが存在する場合は、Painlessスクリプトを使ってupdate_by_query を実行します。

class MigrationName < Elastic::Migration
  include Elastic::MigrationRemoveFieldsHelper

  batched!
  throttle_delay 1.minute

  private

  def index_name
    User.__elasticsearch__.index_name
  end

  def document_type
    'user'
  end

  def fields_to_remove
    %w[two_factor_enabled has_projects]
  end
end

デフォルトのバッチサイズは10_000 です。この値をオーバーライドするにはBATCH_SIZE を指定します:

class MigrationName < Elastic::Migration
  include Elastic::MigrationRemoveFieldsHelper

  batched!
  BATCH_SIZE = 100

  ...
end

Elastic::MigrationObsolete

マイグレーションが不要になった場合に、そのマイグレーションを廃止済みとしてマークします。

class MigrationName < Elastic::Migration
  include Elastic::MigrationObsolete
end

Elastic::MigrationCreateIndex

新しいインデックスを作成します。

必要です:

  • target_class およびdocument_type メソッド。
  • ee/lib/elastic/latest/ のクラスのマッピングとインデックスの設定。ee/lib/elastic/v12p1/
caution
同じマイルストーンでインデックスに値を入力するために、後続のマイグレーションを実行する必要があります。
class MigrationName < Elastic::Migration
  include Elastic::MigrationCreateIndex

  retry_on_failure

  def document_type
    :epic
  end

  def target_class
    Epic
  end
end

Search::Elastic::MigrationReindexBasedOnSchemaVersion

指定 さ れた文書種別が格納 さ れてい る イ ンデ ッ ク ス内のすべての文書の イ ンデ ッ ク ス を再作成 し 、schema_version を更新 し ます。

DOCUMENT_TYPENEW_SCHEMA_VERSION 定数が必要です。インデックスマッピングはYYMM 形式のschema_version 整数フィールドを持たなければなりません。

class MigrationName < Elastic::Migration
  include Search::Elastic::MigrationReindexBasedOnSchemaVersion

  batched!
  batch_size 9_000
  throttle_delay 1.minute

  DOCUMENT_TYPE = WorkItem
  NEW_SCHEMA_VERSION = 23_08
  UPDATE_BATCH_SIZE = 100
end

Elastic::MigrationHelper

マイグレーションがこれまでの例に当てはまらない場合に使用できるメソッドがコンテナに含まれています。

class MigrationName < Elastic::Migration
  include Elastic::MigrationHelper

  def migrate
  ...
  end

  def completed?
  ...
  end
end

がサポートするマイグレーションオプションです。Elastic::MigrationWorker

Elastic::MigrationWorker は以下のマイグレーションオプションをサポートしています:

  • batched! - マイグレーションをバッチで実行できるようにします。設定された場合、Elastic::MigrationWorker は、後述するthrottle_delay オプションで設定される遅延を伴ってそれ自身を再エンキューします。バッチ処理はmigrate メソッドで処理する必要があります。この設定は再エンキューのみを制御します。

  • batch_size - batched! マイグレーション実行中に変更される文書の数を設定します。こ のサ イ ズ は、 更新が完了す る ま でに充分な時間を と る こ と がで き る 値に設定す る 必要があ り ます。こ れは後述のthrottle_delay オプシ ョ ン と 組み合わせて調整する こ と がで き ます。バ ッ チ処理は、 カ ス タ ムのmigrate メ ソ ッ ド か、 ま たは こ の設定を用い るElastic::MigrationBackfillHelper migrate メ ソ ッ ド で行 う 必要があ り ます。デフ ォル ト 値は 1000 文書です。

  • throttle_delay - 待ち時間 バッチ実行間の待ち時間を設定します。こ の時間は、 各マ イ グ リ バ ッ チが完了す る に十分な時間を与え る ために、 十分に長 く 設定す る 必要があ り ます。さらに、この時間はElastic::MigrationWorker cronワーカーが実行する頻度であるため、5分未満であるべきです。デフォルト値は3分です。

  • pause_indexing! - マイグレーション実行中はインデックス作成を一時停止します。この設定はマイグレーションが実行される前にインデックス設定を記録し、マイグレーションが完了したときにその値に戻します。

  • space_requirements! - マイグレーション実行時にクラスターに十分な空き領域があることを確認します。この設定は、マイグレーション実行時に必要なストレージが利用できない場合にマイグレーションを停止します。マイグレーションでは、space_required_bytes メソッドを定義して、必要な容量をバイト単位で提供する必要があります。

  • retry_on_failure - 失敗時の再試行機能を有効にします。デフォルトでは、マイグレーションは30回再試行されます。リトライ回数がなくなると、マイグレーションは中止されたとマークされます。再試行回数をカスタマイズするには、max_attempts 引数を渡します:retry_on_failure max_attempts: 10

# frozen_string_literal: true

class BatchedMigrationName < Elastic::Migration
  # Declares a migration should be run in batches
  batched!
  throttle_delay 10.minutes
  pause_indexing!
  space_requirements!
  retry_on_failure

  # ...
end

マイグレーションにおけるダウンタイムの回避

マイグレーションの取り消し

GitLab.comでマイグレーションが失敗したり停止したりした場合、私たちはマイグレーションを導入した変更を元に戻すことを好みます。こうすることで、セルフマネジメントの顧客が壊れたマイグレーションを受け取ることを防ぎ、バックポートの必要性を減らすことができます。

マージするタイミング

リリースから1週間以内にはマイグレーションをマージしないことを推奨します。これにより、マイグレーションが失敗したり、期待通りに動作しなかったりした場合に、マイグレーションを差し戻す時間を確保できます。リリースの最終週にまだ開発中またはレビュー中のマイグレーションは、次のマイルストーンにプッシュしてください。

複数バージョンの互換性

高度な検索のマイグレーションは、他のGitLabの変更と同様に、複数のバージョンのアプリケーションが同時に稼働している場合をサポートする必要があります。

デプロイの順序によっては、マイグレーションが開始または終了しても、マイグレーション前のアプリケーションコードを実行しているサーバーが残っている可能性があります。すべての高度な検索マイグレーションがデプロイ終了後に開始されるようになるまでは、このことを考慮する必要があります。

リスクの高いマイグレーション

Elasticsearchはトランザクションをサポートしていないため、マイグレーションを開始した後や終了した後にアプリケーションコードが差し戻される事態を想定してマイグレーションを設計する必要があります。

このような理由から、通常、破壊的なアクション(例えば、データを移動した後の削除など)はマイグレーションが正常に完了した後のマージリクエストに延期します。安全のため、自主管理されているお客様については、重要なデータ損失のリスクがある場合は、別のリリースに延期することも必要です。

マイグレーション実行時間の計算

GitLab.comでのマイグレーションの実行にかかる時間を理解することは重要です。マイグレーションによって処理されるドキュメントの数を導き出します。この数は、データベースや既存のElasticsearchインデックスへのクエリから得られるかもしれません。実行時間の計算には以下の式を使います:

> batch_size = 9_000
=> 9000
> throttle_delay = 1.minute
=> 1 minute
> number_of_documents = 15_536_906
=> 15536906
> (number_of_documents / batch_size) * throttle_delay
=> 1726 minutes
> (number_of_documents / batch_size) * throttle_delay / 1.hour
=> 28

高度な検索マイグレーションのベストプラクティス

最良の結果を得るために、以下のベストプラクティスに従ってください:

  • Elastic::MigrationBackfillHelperを使用するマイグレーションの前に、Elastic::MigrationUpdateMappingsHelper を使用するマイグレーションが実行されるように、ドキュメントタイプごとにすべてのマイグレーションを順序付けます。これにより、すべてのマイグレーションが未適用の場合に同じドキュメントのインデックスを何度も再作成することを回避し、バックフィル時間を短縮します。
  • バ ッ チで作業す る 場合は、 バ ッ チサ イ ズ を 9,000 文書以下に保ち ます。バルクインデクサーは毎分実行し、10,000 文書のバッチを処理するように設定します。こうすることで、別のマイグレーションバッチが試行される前に、バルクインデクサーはレコードを処理する時間があります。
  • 文書数が最新であることを確認するために、マイグレーションが完了したかどうかを確認する前にインデックスを更新する必要があります。
  • マイグレーションが開始したとき、完了チェックが発生したとき、そしてマイグレーションが完了したときに、それぞれのマイグレーションにログステートメントを追加します。これらのログはマイグレーションのイシューをデバッグするときに役立ちます。
  • Elasticsearch Reindex APIオペレーションを使用している場合は、インデックス作成を一時停止してください。
  • マイグレーションが失敗する可能性がある場合は、リトライ制限を追加することを検討してください。これにより、イシューが発生した場合にマイグレーションを停止できるようになります。

メジャーバージョンアップにおける高度な検索マイグレーションの削除

高度な検索マイグレーションは通常、長期間にわたって複数のコードパスをサポートする必要があるため、安全にできるときにそれらをクリーンアップすることが重要です。

GitLab のメジャーバージョンアップグレードは、完全にはマイグレーションされていないインデックスの後方互換性を削除する安全なタイミングとして選択します。このことは、アップグレードのドキュメントで説明しています。また、マイグレーションコードを停止したマイグレーションに置き換え、テストを削除することにしました:

  • 高度な検索マイグレーションから呼び出されるコードをメンテナーする必要はありません。
  • もうサポートしないマイグレーションのテストを実行するために CI の時間を浪費することもありません。
  • このマイグレーションを実行しておらず、ターゲットバージョンに直接アップグレードするオペレーションには、インデックスをゼロから再作成するよう促すメッセージが表示されます。

念のため、メジャーアップグレード前の最後のマイナーバージョンで作成されたマイグレーションは削除しません。ですから、%14.0にアップグレードする場合は、%13.12で追加されたマイグレーションを削除してはいけません。このようなセーフティネットがあることで、GitLab.comでのマイグレーションが完了するまでに何週間もかかるような場合にも対応することができます。%13.12 のマイグレーションが完了する前に GitLab.com を%14.0 にアップグレードしてしまったら大変です。GitLab.comへのデプロイは自動化されており、これを防ぐための自動チェックもありません。さらに、仮にこれを防ぐための自動チェックがあったとしても、高度な検索のマイグレーションで GitLab.com のデプロイを止めたいとは思いません。

マイグレーションを削除する手順

アップグレード先のメジャーバージョンより2つ前のマイナーバージョンで作成されたすべてのマイグレーションについて、以下の処理を行います:

  1. GitLab.com へのマイグレーションが正常に完了したことを確認します。
  2. マイグレーションの内容を置き換えます:

    include Elastic::MigrationObsolete
    
  3. このマイグレーションをサポートする仕様ファイルをすべて削除してください。
  4. このマイグレーションの後方互換性を処理するロジックを削除してください。このロジックはElastic::DataMigrationService.migration_has_finished?(:migration_name_in_lowercase)で見つけることができます。
  5. これらの変更でマージリクエストを作成してください。メジャーリリースが開始される前に誤ってマージしないように注意してください。