1. 背景
2020 年以降、コンテンツ アノテーション結果の検索は、コミュニティのバックエンド ビジネスの中核となる高頻度の利用シナリオの 1 つとなっており、複雑なバックエンド検索をサポートするために、キー情報の追加コピーを保存しています。 Elasticsearch のコミュニティ コンテンツを二次インデックスとして使用します。アノテーション ビジネスの細分化、反復、時間の経過に伴い、インデックス付けされた文書の数と検索の RT が徐々に増加し始めました。現在の本指数の監視状況は以下の通りです。
この記事では、コミュニティがIndexSorting を使用して、最初の 2000 ミリ秒から 50 ミリ秒までの 10 億レベルのドキュメントの検索パフォーマンスを最適化する方法を紹介します。同様の問題やシナリオに遭遇した場合、これを読んだ後は、1 行のコードから莫大な収入を得ることができると思います。
2. 探査プロセス
2.1 初期最適化
最初の要件は非常に単純で、最新の動的ページング表示を取得するだけです。このとき、実装も機能さえ満たしていれば簡単で大雑把です。クエリ文は次のとおりです。
{
"track_total_hits": true,
"sort": [
{
"publish_time": {
"order": "desc"
}
}
],
"size": 10
}
ホームページを読み込む際にフィルタ条件が追加されていないため、10億レベルのコンテンツライブラリから最新の公開コンテンツ10件を検索するようになりました。
このクエリでは、大きな結果セットの並べ替えで問題が発生していることが簡単にわかります。問題を解決するには、次の 2 つの方法が自然に考えられます。
- 並べ替えを削除する
- 結果セットを絞り込む
ユーザーの要望と開発コストを天秤にかけた結果、「まず保持してから最適化する」ことを決定し、ユーザーがホームページを開くと、デフォルトで「過去 1 週間以内に公開されたもの」というフィルター条件が追加されます。は次のようになります:
{
"track_total_hits": true,
"query": {
"bool": {
"filter": [
{
"range": {
"publish_time": {
"gte": 1678550400,
"lt": 1679155200
}
}
}
]
}
},
"sort": [
{
"publish_time": {
"order": "desc"
}
}
],
"size": 10
}
この変更の開始後、その効果はすぐに現れたと言え、ホームページの読み込み速度はすぐに 200 ミリ秒未満に低下し、平均 RT60 ミリ秒になりました。この変更により、ビジネスからのプレッシャーも軽減され、その後の最適化のために多くの研究時間を得ることができました。
検索ホームページの読み込み速度は明らかに速くなりますが、実際には根本的な問題は解決されません。ES の大きな結果セット内の指定されたフィールドの並べ替えは依然として非常に遅いのです。ビジネスの場合、エクスポートや完全な動的検索など、結果ページの一部の境界機能のエクスペリエンスはまだ満足のいくものではありません。この点はモニタリングからも明確にわかります。遅いクエリは依然として存在しており、少数のインターフェイス タイムアウトを伴います。
正直に言うと、この時期の ES についての理解はまだ比較的基礎的なものであり、ES を使用できる、シャーディング、転置インデックス、相関スコアリングについて知っていると言うだけで、それは消えてしまいます。つまり、方向性が決まり、追いつき始めたのです。
2.2 精密研削
2.2.1 知識の蓄積
以前の問題が残ったまま、ESを一から勉強し直しました。検索のパフォーマンスを最適化するには、まず検索がどのように機能するかを知る必要があります。最も単純な検索を例として、検索リクエストのプロセス全体を分解してみましょう。
(1) 検索リクエスト
{
"track_total_hits":false,
"query": {
"bool": {
"filter": [
{
"term": {
"category_id.keyword": "xxxxxxxx"
}
}
]
}
},
"size": 10
}
category_id が「xxxxxxxx」であるドキュメントを正確にクエリし、10 個のデータを取得します。並べ替えや合計数は必要ありません。
全体的なプロセスは 3 つのステップに分かれています。
- クライアントが Node1 へのリクエストを開始します
- Node1 は調整ノードとして、インデックスの各プライマリ シャードまたはセカンダリ シャードにリクエストを転送し、各シャードがクエリをローカルで実行します。
- 各ノードは独自のデータを返し、調整ノードが要約してクライアントに返します。
このプロセスは大まかに図で表すと次のようになります。
ES は Lucene が提供する機能に依存していることはわかっていますが、実際の検索は Lucene で行われるため、引き続き Lucene の検索プロセスを理解する必要があります。
(2)ルセン
Lucene には、次の 4 つの基本データ型が含まれています。
- インデックス: 多数のドキュメントで構成されるインデックス。
- ドキュメント: 多くのフィールドで構成され、インデックスと検索の最小単位です。
- フィールド: フィールド名やフィールド値など、多くの用語で構成されます。
- 用語: 多数のバイトで構成されます。一般に、Text型のField Valueを単語分割した後の最小単位をTermと呼びます。
Lucene インデックスの検索プロセスを紹介する前に、Lucene インデックスを構成する最小のデータ ストレージ ユニットであるセグメントについて説明します。
Lucene インデックスは多くのセグメントで構成されており、各セグメントにはドキュメントの用語辞書、用語辞書の反転リスト、ドキュメントの列形式ストレージ DocValues、および前方インデックスが含まれます。これは、Lucene インデックスの縮小版に近い検索機能を外部に独立して直接提供できます。
(3) 用語辞典と掲載リスト
上の図は、用語辞書とその反転リストの一般的な外観を示しています。もちろん、ここには次のような重要なデータ構造がいくつかあります。
- FST: 用語インデックス、内蔵メモリ。単一の用語、用語の範囲、用語のプレフィックス、ワイルドカード クエリを迅速に実現できます。
- BKD-Tree: 数値型 (空間点を含む) の高速検索用。
- SkipList: 転置リストのデータ構造
ここには多くの詳細があり、興味のある方は別途学習してください。これは全体的な検索プロセスには影響しませんが、ここで繰り返します。用語辞書と反転リストを使用すると、検索条件に一致する結果セットを直接取得できます。次に、docID を使用して前方インデックスからドキュメント全体を取得して返すだけです。これは、ES の基本ディスクは理論的には遅くならないためで、ソート時にクエリが遅くなるのではないかと推測されます。では、リクエストに並べ替えを追加するとどうなるでしょうか? 例えば:
{
"track_total_hits":false,
"query": {
"bool": {
"filter": [
{
"term": {
"category_id.keyword": "xxxxxxxx"
}
}
]
}
},
"sort": [
{
"publish_time": {
"order": "desc"
}
}
],
"size": 10
}
反転リストから取得した docId の順序が乱れています。並べ替えフィールドが指定されたので、最も簡単で直接的な方法は、すべての docId を抽出して、上位 10 個を並べ替えることです。確かに効果は得られますが、効率は考えられます。では、Lucene はそれをどのように解決するのでしょうか?
(4)DocValues
転置インデックスは単語から文書への高速マッピングを解決できますが、検索結果の分類、並べ替え、数学的計算などの集計操作が必要な場合は、文書番号から値への高速マッピングが必要です。プラス指数が膨らみすぎているのですが、どうすればよいでしょうか?
この時点では、皆さんは直接列指向ストレージを思い浮かべるかもしれませんが、それは何も問題ありません。Lucene は、docId—DocValues に基づいた列指向ストレージ構造を導入しました。
書類番号 | 列の値 | 列値のマッピング |
---|---|---|
0 | 2023-01-13 | 2 |
1 | 2023-01-12 | 1 |
2 | 2023-03-13 | 3 |
たとえば、上の表の DocValues=[2023-01-13, 2023-01-12,2023-03-13]
列の値が文字列の場合、Lucene は元の文字列値を辞書に従ってソートしてデジタル ID を生成します。このような前処理により、ソート速度がさらに向上します。したがって、DocValues=[2, 1, 3] を取得しました。
Docvalues の列形式のストレージ形式により、トラバースを高速化できます。この時点で、最初の N レコードをフェッチする通常の検索リクエストは、実際の解体の完了と見なすことができます。単語の頻度、関連性のスコアリング、集計、その他の機能の分析についてはここでは説明しません。そのため、この記事ではプロセス全体とデータ構造を大幅に簡略化します。この部分にご興味がございましたら、ぜひ一緒に議論してください。
現時点では、ソートが遅いという問題が徐々に表面化しています。Docvalues は列型ストレージであり、クエリ中の複雑な比較を避けるために複雑な値を単純な値に前処理しますが、ソートする必要がある大規模なデータセットを保持することはできません。 . .
ES は最善を尽くしているようですが、私たちのシーンでの遅いクエリの問題を解決するのが本当に苦手なようです。
しかし、スピリチュアルな読者は、逆リストを事前に指定した順序で保存できれば、並べ替えにかかる時間をすべて節約できると考えたに違いありません。
2.2.2 インデックスのソート
間もなく、ES 公式ドキュメント「検索速度の調整方法」で、検索最適化手法であるインデックス ソーティング (インデックス ソーティング) が私たちの視野に現れました。
このドキュメントの説明から、インデックスの並べ替えによって主に次の 2 つの側面で検索パフォーマンスが向上することがわかります。
- 複数条件の並列クエリ (a と b と ...) の場合、インデックス ソートは、修飾されていないドキュメントをまとめて保存し、多数の不一致のドキュメントをスキップするのに役立ちます。ただし、このトリックは、フィルタリングによく使用されるカーディナリティの低いフィールドに対してのみ機能します。
- 早期ブレイク: 検索ソートとインデックスソートで指定された順序が同じ場合、各セグメントの最初の N 個のドキュメントのみを比較する必要があり、他のドキュメントは合計の計算にのみ使用する必要があります。例: 文書内にタイムスタンプがあり、タイムスタンプに基づいて検索と並べ替えが必要になることがよくありますが、このとき、指定されたインデックスの並べ替えが検索の並べ替えと一致していれば、通常、検索と並べ替えの効率が大幅に向上します。改善されました。
早めの中断!!!それは単純に欠けていたものでした。そこで私たちはこれを中心に研究を始めました。
(1) インデックスソートを有効にする
{
"settings": {
"index": {
"sort.field": "publish_time", // 可指定多个字段
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"content_id": {
"type": "long"
},
"publish_time": {
"type": "long"
},
...
}
}
}
上の例のように、ドキュメントはディスクに書き込まれるときに、publish_time フィールドの降順で並べ替えられます。
前の段落で、docID とポジティブインデックスについて繰り返し説明しました。ちなみに両者の関係を簡単にご紹介すると、まずセグメント内の各文書には docID が割り当てられますが、docID は 0 から順に割り当てられます。IndexSorting が設定されていない場合は、文書の作成順に docID が割り当てられますが、IndexSorting を設定すると、docID の順序は IndexSorting の順序と一致します。
次の図は、docID と前方インデックスの関係を示しています。
そこで、元のクエリをもう一度見てみましょう。
{
"track_total_hits":true,
"sort": [
{
"publish_time": {
"order": "desc"
}
}
],
"size": 10
}
Lucene でクエリを実行すると、結果セットの反転リストの順序は、publish_time の降順になっていることがわかります。そのため、最初の 10 個のデータがクエリされた後にクエリを返すことができます。これにより、早期の中断が実現され、並べ替えのオーバーヘッドが節約されます。 。それで、価格はいくらですか?
(2) 検討事項
IndexSorting はクエリ時の並べ替えとは異なり、本質的には書き込み時にデータを前処理することです。したがって、並べ替えフィールドは作成時にのみ指定でき、変更することはできません。また、書き込み時にデータを並べ替える必要があるため、書き込みパフォーマンスにも一定の悪影響を及ぼします。
Lucene 自体にはソートに関するさまざまな最適化機能があることを前述しました。そのため、検索結果セット自体にそれほど多くのデータが含まれていない場合、この機能が有効になっていない場合でも、良好な RT が得られる可能性があります。
さらに、ほとんどの場合、合計数を計算する必要があるため、インデックス ソートを有効にした後は、ソート プロセスを事前に中断するか、結果セットの合計数をカウントする必要があります。合計数を確認できない場合、または別の方法で合計数を取得できない場合は、この機能を有効に活用できます。
まとめ:
- 大規模な結果セットに対して上位 N 項目が並べ替えられるシナリオでは、インデックスの並べ替えにより検索パフォーマンスが大幅に向上します。
- インデックスの並べ替えはインデックスの作成時にのみ指定でき、変更することはできません。指定されたフィールドの並べ替えシナリオが複数ある場合は、並べ替えフィールドを慎重に選択する必要がある場合があります。
- 合計数を取得しない場合は、インデックス ソートを有効に活用できます。
- インデックスのソートを有効にすると、書き込みパフォーマンスがある程度低下します。参考までに、ElasticsearchBenchmarks データのスクリーンショットを次に示します。
见:Elasticsearch ベンチマーク
2.3 効果
私たちのビジネスは ES 書き込みのボトルネックには遠く及ばず、並べ替えフィールドが頻繁に変更されるシナリオはほとんどありません。短いトレードオフの後、インデックス ソートがまさに私たちが必要としていたものであると判断したため、オンラインの実データを使用して、インデックス ソートの効果に関する簡単なパフォーマンス テストを実施し始めました。
(1) 性能テスト:ホームページ
(2) 性能試験:その他
ここでは、インデックスの並べ替えがオンになった後、いくつかの一般条件と時間枠の検索組み合わせテストがランダム化されます。
効果が非常に明確で、以前のような急上昇もなく、RTも非常に安定していることがわかり、この機能を正式にリリースすることにしました。
(3) オンライン効果
遅いクエリ
!
全体的な前後比較
基本的に予想通り、検索RTが大幅に減り、クエリの遅さは完全になくなりました。
2.4 その後の最適化
実際、検討の過程で他の最適化手法もいくつか見つかりましたが、開発コストやメリットを考慮すると、本番環境に十分に適用されていないものもあります。インスピレーションを与えられることを願って、その一部をここに紹介します。
- 合計数を取得しない: ほとんどのシナリオでは、合計数をクエリしないことでオーバーヘッドが削減され、パフォーマンスが向上します。ES 7.x 以降の検索インターフェイスでは、デフォルトでは合計数が返されませんが、これは明らかです。
- カスタム ルーティング ルール: 上記のクエリ プロセスから、ES がすべてのフラグメントをポーリングして必要なデータを取得することがわかります。データ フラグメントの場所を制御できれば、多くのオーバーヘッドも節約できます。例: 将来、特定のユーザーのダイナミクスをチェックする多数のシナリオがある場合、ユーザーごとにシャーディングを制御できます。これにより、シャード ポーリングが回避され、検索効率が向上します。
- キーワード: すべての数値を数値フィールドとして保存する必要はありません。数値が範囲クエリにはめったに使用されないが、用語クエリにはよく使用され、検索 rt の影響を受けやすい場合。キーワードは最適な保存方法です。
- データの前処理: IndexSoting と同様に、書き込み時にデータを前処理できれば、検索のオーバーヘッドも節約できます。この組み合わせにより、
_ingest/pipeline
予期せぬ効果が生じる可能性があります。
3.最後に書く
これを見ていただければ、私たちの最適化はそれほど高度な技術的な困難を伴うものではなく、単に問題を解決していく過程にあり、初心者から徐々に初心者に変わってきたことがおわかりいただけると思います。大きな牛に来るのは最初から回り道を通らないかもしれないが、千里の道も一歩から始まる 最後に、初心者の方への参考になればと思い、皆さんと共有したい経験や感想をまとめておきます。私たちのような。
ES は、大規模な結果セットがフィールドの並べ替えを指定するシナリオでは適切にパフォーマンスを発揮しないため、ES を使用する場合はそのようなシナリオを避けるようにする必要があります。これが避けられない場合は、IndexSorting を適切に設定すると、ソートのパフォーマンスが大幅に向上します。
最適化には終わりがありません。コストとメリットを比較検討し、最も優先度の高い重要な問題を解決するためにリソースを集中する必要があります。
文:昆布
この記事は Dewu technology のオリジナルに属し、出典: Dewu technology 公式ウェブサイト
Dewu Technology の許可なく転載することは固く禁じられています。そうでない場合は、法律に従って法的責任を調査されます。著作権は作者に帰属します。商業的転載の場合は著者に連絡して承認を求め、非商業的転載の場合は出典を明記してください。