- ディープダイブ
- サポートされているバージョン
- 開発者の環境設定
- 役立つ Rake タスク
- どのように動作しますか?
- 既存のアナライザとトークナイザ
- 注意点
- 複数インデックスによるダウンタイムなしの再インデックス作成
- パフォーマンス・モニタリング
- トラブルシューティング
高度な検索開発者のためのガイドライン
このページでは、Elasticsearch の開発者向けの情報を提供します。
Elasticsearch を有効にして最初のインデックス作成を行う方法についてはElasticsearch インテグレーションドキュメント を参照してください。
ディープダイブ
2019年6月、Mario de la OssaがGitLabElasticsearchインテグレーションに関するDeep Dive(GitLabチームメンバー限定:https://gitlab.com/gitlab-org/create-stage/-/issues/1
)を開催し、将来コードベースのこの部分に携わる可能性のある人に彼のドメイン固有の知識を共有しました。詳しくは 録画は YouTube で、スライドはGoogle SlidesとPDFで見ることができます。このディープダイブで扱った内容は全てGitLab 12.0時点でのもので、具体的な詳細は変更されているかもしれませんが、それでも良い入門書として役立つはずです。
2020年8月には、マルチインデックスをサポートするためのGitLab特有のアーキテクチャに焦点を当てた2回目のディープダイブが開催されました。その YouTubeでの録画と スライドが利用可能です。このディープダイブで取り上げた内容は全てGitLab 13.3時点でのものです。
サポートされているバージョン
バージョン要件を参照してください。
Elasticsearch のクエリに大きな変更を加える開発者は、サポートされているすべてのバージョンでその機能をテストしてください。
開発者の環境設定
Elasticsearch GDKのセットアップ手順を参照してください。
役立つ Rake タスク
-
gitlab:elastic:test:index_size
:現在のインデックスが使用している容量と、インデックス内のドキュメント数を表示します。 -
gitlab:elastic:test:index_size_change
:インデックスサイズを出力し、インデックスを再作成し、再度インデックスサイズを出力します。インデックスサイズの改善をテストする際に便利です。
さらに、テスト用に大きなリポジトリや複数のフォークが必要な場合は、以下の手順を検討してください。
どのように動作しますか?
Elasticsearch インテグレーションは外部のインデクサに依存しています。私たちはGoで書かれたインデクサを出荷しています。ユーザーは Rake タスクによって最初のインデックス作成をトリガーする必要がありますが、これが完了した後は、GitLab 自身が/ee/app/models/concerns/elastic/application_versioned_search.rb
から継承された作成、更新、破棄時のafter_
コールバックによって、必要に応じてインデックス作成をトリガーします。
最初のインデックス作成が完了すると、プロジェクト(#207494 を参照)を除くすべてのモデルの作成・更新・削除オペレーションが RedisZSET
で追跡されます。通常のsidekiq-cron
ElasticIndexBulkCronWorker
はこのキューを処理し、Bulk Request APIを使って一度に多くの Elasticsearch ドキュメントを更新します。
検索クエリはee/app/models/concerns/elastic
にある関係によって生成されます。これらの懸念はアクセス制御も担当しており、歴史的にセキュリティバグの原因となっていますので、細心の注意を払ってください!
カスタムルーティング
Elasticsearch では、プロジェクトに関連するドキュメントタイプに対してカスタムルーティングを使用します。ルーティングの形式はproject_<project_id>
です。ルーティングはインデックス作成と検索オペレーション中に設定されます。カスタムルーティングを使用する利点とトレードオフの例をいくつか挙げます:
- プ ロ ジ ェ ク ト ス コ ープ さ れた検索は非常に高速にな り ます。
- グローバル検索やグループ検索でヒットするシャードの数が多すぎる場合は、ルーティングは使用されません。
- シャードサイズの不均衡が発生する可能性があります。
既存のアナライザとトークナイザ
ee/lib/elastic/latest/config.rb
では、以下のアナライザおよびトークナイザが定義されています。
アナライザ
path_analyzer
blobのパスにインデックスを付けるときに使用します。path_tokenizer
、lowercase
、asciifolding
フィルタを使用します。
例については、以下のpath_tokenizer
の説明を参照してください。
sha_analyzer
blob とコミットで使用されます。sha_tokenizer
、lowercase
、asciifolding
フィルタを使用します。
例については、後述のsha_tokenizer
の説明を参照してください。
code_analyzer
blobのファイル名とコンテンツをインデックスするときに使用されます。whitespace
、word_delimiter_graph
、lowercase
、asciifolding
フィルタを使用します。
トークンの分割方法をより制御できるように、whitespace
トークナイザーが選択されました。たとえば、文字列Foo::bar(4)
を適切に検索するには、Foo
やbar(4)
のようなトークンを生成する必要があります。
トークンの分割方法についてはcode
フィルタをご覧ください。
code_analyzer
はすべてのコードケースを考慮しているわけではありません。トークナイザー
sha_tokenizer
これは、edgeNGram
トークナイザ を使用して、SHA をその任意のサブセット(最小 5 文字)で検索できるようにするカスタム・トークナイザです。
使用例:
240c29dc7e
になります:
240c2
240c29
240c29d
240c29dc
240c29dc7
240c29dc7e
path_tokenizer
これはカスタム・トークナイザーで、path_hierarchy
トークナイザー とreverse: true
を使用し、入力として与えられたパスの多寡にかかわらず検索でパスを見つけることができるようにします。
使用例:
'/some/path/application.js'
になります:
'/some/path/application.js'
'some/path/application.js'
'path/application.js'
'application.js'
注意点
- 検索は独自のアナライザを持つことができます。アナライザーを編集する際は、忘れずにチェックしてください。
-
Character
トークン・フィルターとは対照的に)フィルターは常に元の文字を置き換えます。これらのフィルターは正確な検索を妨げます。
複数インデックスによるダウンタイムなしの再インデックス作成
現在のところ、GitLabは単一のバージョンの設定しか扱うことができません。設定やスキーマを変更した場合、すべてを一からインデックスし直す必要があります。インデックスの再作成には時間がかかるため、検索機能のダウンタイムが発生する可能性があります。
ダウンタイムを避けるために、GitLabは同時に機能する複数のインデックスをサポートするように取り組んでいます。スキーマが変更されるたびに、管理者は新しいインデックスを作成し、そのインデックスを再作成することができます。データの更新は両方のインデックスに転送されます。新しいインデックスの準備ができたら、管理者はそのインデックスをアクティブにすることができます。
これは、AWSへの移行など、新しいサーバーへのマイグレーションにも便利です。
現在、私たちはこの新しいデザインにマイグレーションしているところです。今のところ、すべてが1つのバージョンで動作するようにハードワイヤードされています。
アーキテクチャ
elasticsearch-rails
によって提供される伝統的なセットアップは、内部プロキシ・クラスを通して通信することです。開発者はモデル固有のロジックを、モデルに組み込むためのモジュール(例えばSnippetsSearch
)に記述します。__elasticsearch__
のメソッドは、例えばプロキシオブジェクトを返します:
-
Issue.__elasticsearch__
のインスタンスを返します。Elasticsearch::Model::Proxy::ClassMethodsProxy
-
Issue.first.__elasticsearch__
はElasticsearch::Model::Proxy::InstanceMethodsProxy
のインスタンスを返します。
これらのプロキシオブジェクトは Elasticsearch サーバと直接通信します(図の上半分を参照)。
新しい設計では、各モデルには対応するサブクラス化されたプロキシオブジェクトのペアがあり、その中にモデル固有のロジックが配置されます。例えば、Snippet
はElasticsearch::Model::Proxy::ClassMethodsProxy
のサブクラスであるSnippetClassProxy
を持ちます。Snippet
はElasticsearch::Model::Proxy::InstanceMethodsProxy
のサブクラスであるSnippetInstanceProxy
を持ちます。
__elasticsearch__
はプロキシオブジェクトの別のレイヤーを表し、複数の実際のプロキシオブジェクトを追跡します。これはメソッドコールを適切なインデックスに転送します。例えば
-
model.__elasticsearch__.search
は安定したインデックスに転送されます。 -
model.__elasticsearch__.update_document
はすべてのインデックスに転送されます。
バージョンごとのグローバル設定はElastic::(Version)::Config
。そこでマッピングを変更できます。
新しいバージョンのスキーマの作成
ee/lib/elastic/v12p1
のようなフォルダには、異なるバージョンの検索ロジックのスナップショットが格納されています。Git の履歴を継続的に管理するために、最新バージョンはee/lib/elastic/latest
の下にありますが、そのクラスは実際のバージョン (例えばee/lib/elastic/v12p3
) の下にエイリアスされています。これらのクラスを参照するときは、Latest
名前空間を直接使用せず、実際のバージョンを使用してください (たとえば、V12p3
)。
バージョン名は基本的に GitLab のリリースバージョンに従います。12.3で設定が変更された場合、V12p3
(pは “point “の略)という新しい名前空間を作成します。バージョン名を変更する必要がある場合は、イシューを発行してください。
現在のバージョンがv12p1
で、v12p3
の新しいバージョンを作成する必要がある場合、手順は次のようになります:
-
v12p1
のフォルダ全体をv12p3
-
v12p3
フォルダー下のファイルの名前空間をV12p1
からV12p3
に変更します(エイリアスはLatest
のままです)。 -
v12p1
フォルダーを削除します。 -
latest
のフォルダ全体をv12p1
-
v12p1
フォルダー下のファイルの名前空間をLatest
から次のように変更します。V12p1
- 必要に応じて、
latest
フォルダ下のファイルを変更します。
パフォーマンス・モニタリング
Prometheus
GitLabは、すべてのWeb/APIリクエストとSidekiqジョブのリクエスト数とタイミングに関連するPrometheusメトリクスをエクスポートし、パフォーマンスの傾向を診断したり、Elasticsearchのタイミングが他のことに費やした時間と比較してパフォーマンス全体にどのような影響を与えているかを比較したりするのに役立ちます。
インデックスキュー
GitLabはインデックスキューのPrometheusメトリクスもエクスポートし、パフォーマンスのボトルネックを診断したり、GitLabインスタンスやElasticsearchサーバーが更新量に追いついているかどうかを判断するのに役立ちます。
ログ
インデックス作成はすべてSidekiqで行われるため、Elasticsearchインテグレーションに関連するログの多くはsidekiq.log
。特に、何らかの方法でElasticsearchにリクエストを行うすべてのSidekiqワーカーは、リクエスト数とElasticsearchへのクエリ/書き込みにかかった時間をログに記録します。これは、クラスターがインデックス作成に追いついているかどうかを把握するのに役立ちます。
Elasticsearchの検索は通常のウェブワーカーがリクエストを処理することで行われます。ページの読み込みやAPIリクエストを行い、それがElasticsearchへのリクエストとなった場合、リクエスト数とproduction_json.log
にかかった時間がログに記録されます。これらのログにはDatabaseとGitalyのリクエストにかかった時間も記録され、検索のどの部分のパフォーマンスが低いかを診断するのに役立ちます。
また、elasticsearch.log
に送信される Elasticsearch 固有のログには、パフォーマンスの問題を診断するのに役立つ情報が含まれている可能性があります。
パフォーマンスバー
Elasticsearch のリクエストはPerformance Bar
に表示されます。これは開発者のローカルでも、デプロイされた GitLab インスタンスでも、検索パフォーマンスの低下を診断するために使用できます。これは正確なクエリが表示されるので、検索が遅い原因を診断するのに便利です。
相関 ID とX-Opaque-Id
相関 IDは Rails から Elasticsearch へのすべてのリクエストでX-Opaque-Id
ヘッダとして転送され、GitLab でクラスター内のすべてのタスクを追跡できるようになります。
トラブルシューティング
取得flood stage disk watermark [95%] exceeded
次のようなエラーが表示されることがあります。
[2018-10-31T15:54:19,762][WARN ][o.e.c.r.a.DiskThresholdMonitor] [pval5Ct]
flood stage disk watermark [95%] exceeded on
[pval5Ct7SieH90t5MykM5w][pval5Ct][/usr/local/var/lib/elasticsearch/nodes/0] free: 56.2gb[3%],
all indices on this node will be marked read-only
これは、ディスク容量のしきい値を超えたためです。デフォルトの95%のしきい値に基づいて、十分なディスク容量が残っていないと判断されます。
さらに、read_only_allow_delete
の設定は、true
に設定されます。 これは、forcemerge
などのインデックス作成をブロックします。
curl "http://localhost:9200/gitlab-development/_settings?pretty"
これをelasticsearch.yml
ファイルに追加してください:
# turn off the disk allocator
cluster.routing.allocation.disk.threshold_enabled: false
または
# set your own limits
cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.flood_stage: 5gb # ES 6.x only
cluster.routing.allocation.disk.watermark.low: 15gb
cluster.routing.allocation.disk.watermark.high: 10gb
Elasticsearch を再起動すると、read_only_allow_delete
は自動的にクリアされます。
ディスクベースシャードアロケーション|Elasticsearchリファレンス」5.6および6.xより
ディザスタリカバリ/データロス/バックアップ
GitLabにおけるElasticsearchの使用は、セカンダリデータストアとしてのみです。つまり、Elasticsearchに保存されたデータはすべて、他のデータソース、特にPostgreSQLとGitalyから常に再導出することができます。そのため、何らかの理由でElasticsearchのデータストアが破損した場合でも、ゼロからインデックスを作成し直すことができます。
Elasticsearchのインデックスがとてつもなく大きい場合、ゼロからインデックスを作成し直すのは時間がかかりすぎたり、ダウンタイムが長すぎたりする可能性があります。Elasticsearchインデックスの同期が取れなくなった場合に、自動的に矛盾を見つけて再同期する仕組みは組み込まれていませんが、有用なツールの1つは、見逃していると思われる時間範囲に発生したすべての更新のログを調べることです。この情報は非常に低レベルで、GitLabのコードベースに精通しているオペレーションにしか役に立ちません。他の人の役に立つかもしれないので、ここに記録しておきます。理論的には、何が再生される必要があるのかを知るために使うことができる関連ログは次のとおりです:
- 同期されたリポジトリ以外の更新は、
track_items
を検索することでelasticsearch.log
から見つけることができます。::Elastic::ProcessBookkeepingService.track!
- 発生したすべてのリポジトリ更新は、
indexing_commit_range
を検索することでelasticsearch.log
で見つけることができます。これらを再生するには、IndexStatus#last_commit/last_wiki_commit
をログの最も古いfrom_sha
にリセットし、次のようにプロジェクトの別のインデックスをトリガーする必要があります。ElasticCommitIndexerWorker
- 発生したすべてのプロジェクト削除は、
ElasticDeleteProjectWorker
を検索することでsidekiq.log
で見つけることができます。これらの更新は、別のElasticDeleteProjectWorker
をトリガーすることで再生できます。
上記の方法と定期的なElasticsearch スナップショットの取得により、ゼロからインデックスを作成するよりも比較的短時間で様々な種類のデータ損失問題から復旧できるはずです。