検索速度の調整

ファイルシステムキャッシュにメモリを割り当てる

Elasticsearchは検索を迅速に行うためにファイルシステムキャッシュに大きく依存しています。一般的に、利用可能なメモリの少なくとも半分をファイルシステムキャッシュに割り当てることを確認する必要があります。これにより、Elasticsearchはインデックスのホットな領域を物理メモリに保持できます。

Linuxで控えめなリードアヘッド値を使用してページキャッシュのスラッシングを避ける

検索は多くのランダムな読み取りI/Oを引き起こす可能性があります。基盤となるブロックデバイスのリードアヘッド値が高い場合、特にファイルがメモリマッピングを使用してアクセスされるときに、多くの不必要な読み取りI/Oが行われる可能性があります(ストレージタイプを参照)。

ほとんどのLinuxディストリビューションは、単一のプレーンデバイスに対して128KiBの妥当なリードアヘッド値を使用しますが、ソフトウェアRAID、LVM、またはdm-cryptを使用する場合、結果として得られるブロックデバイス(Elasticsearchのパスデータ)は非常に大きなリードアヘッド値(数MiBの範囲)を持つことがあります。これにより、通常、ページ(ファイルシステム)キャッシュのスラッシングが発生し、検索(または更新)のパフォーマンスに悪影響を及ぼします。

現在の値はKiBlsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZEを使用して確認できます。この値を変更する方法については、ディストリビューションのドキュメントを参照してください(たとえば、再起動を跨いで持続するためのudevルールを使用するか、blockdev —setraを一時設定として使用します)。リードアヘッドには128KiBの値を推奨します。

blockdevは512バイトセクターで値を期待し、lsblkKiBで値を報告します。例として、/dev/nvme0n1のためにリードアヘッドを128KiBに一時的に設定するには、blockdev --setra 256 /dev/nvme0n1を指定します。

より高速なハードウェアを使用する

検索がI/Oに制約されている場合は、ファイルシステムキャッシュのサイズを増やす(上記を参照)か、より高速なストレージを使用することを検討してください。各検索は、複数のファイルにわたる順次およびランダムな読み取りの混合を含み、各シャードで同時に多くの検索が実行される可能性があるため、SSDドライブは回転ディスクよりもパフォーマンスが向上する傾向があります。

検索がCPUに制約されている場合は、より多くの高速CPUを使用することを検討してください。

ローカルストレージとリモートストレージ

直接接続された(ローカル)ストレージは、リモートストレージよりも一般的にパフォーマンスが良好です。これは、適切に構成するのが簡単で、通信オーバーヘッドを回避できるためです。

一部のリモートストレージは、特にElasticsearchが課すような負荷の下では非常にパフォーマンスが悪いです。しかし、慎重に調整すれば、リモートストレージを使用しても受け入れ可能なパフォーマンスを達成できることがあります。特定のストレージアーキテクチャにコミットする前に、現実的なワークロードでシステムをベンチマークして、調整パラメータの影響を確認してください。期待するパフォーマンスを達成できない場合は、ストレージシステムのベンダーと協力して問題を特定してください。

ドキュメントモデリング

ドキュメントは、検索時の操作ができるだけ安価になるようにモデル化する必要があります。

特に、結合は避けるべきです。 nestedはクエリを数倍遅くする可能性があり、親子関係はクエリを数百倍遅くする可能性があります。したがって、同じ質問が結合なしでドキュメントを非正規化することで回答できる場合、重要な速度向上が期待できます。

できるだけ少ないフィールドを検索する

query_stringまたはmulti_matchクエリがターゲットとするフィールドが多いほど、遅くなります。複数のフィールドにわたる検索速度を向上させる一般的な手法は、インデックス時にその値を単一のフィールドにコピーし、検索時にこのフィールドを使用することです。これは、ドキュメントのソースを変更することなく、マッピングのcopy-toディレクティブを使用して自動化できます。以下は、映画を含むインデックスの例で、映画の名前とプロットの両方をname_and_plotフィールドにインデックスすることで、クエリを最適化しています。

Python

  1. resp = client.indices.create(
  2. index="movies",
  3. mappings={
  4. "properties": {
  5. "name_and_plot": {
  6. "type": "text"
  7. },
  8. "name": {
  9. "type": "text",
  10. "copy_to": "name_and_plot"
  11. },
  12. "plot": {
  13. "type": "text",
  14. "copy_to": "name_and_plot"
  15. }
  16. }
  17. },
  18. )
  19. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'movies',
  3. body: {
  4. mappings: {
  5. properties: {
  6. name_and_plot: {
  7. type: 'text'
  8. },
  9. name: {
  10. type: 'text',
  11. copy_to: 'name_and_plot'
  12. },
  13. plot: {
  14. type: 'text',
  15. copy_to: 'name_and_plot'
  16. }
  17. }
  18. }
  19. }
  20. )
  21. puts response

Js

  1. const response = await client.indices.create({
  2. index: "movies",
  3. mappings: {
  4. properties: {
  5. name_and_plot: {
  6. type: "text",
  7. },
  8. name: {
  9. type: "text",
  10. copy_to: "name_and_plot",
  11. },
  12. plot: {
  13. type: "text",
  14. copy_to: "name_and_plot",
  15. },
  16. },
  17. },
  18. });
  19. console.log(response);

コンソール

  1. PUT movies
  2. {
  3. "mappings": {
  4. "properties": {
  5. "name_and_plot": {
  6. "type": "text"
  7. },
  8. "name": {
  9. "type": "text",
  10. "copy_to": "name_and_plot"
  11. },
  12. "plot": {
  13. "type": "text",
  14. "copy_to": "name_and_plot"
  15. }
  16. }
  17. }
  18. }

データを事前インデックス化する

クエリのパターンを活用して、データのインデックス化方法を最適化する必要があります。たとえば、すべてのドキュメントにpriceフィールドがあり、ほとんどのクエリが固定リストの範囲でrange集約を実行する場合、この集約をインデックスに範囲を事前インデックス化してterms集約を使用することで、より高速にすることができます。

たとえば、ドキュメントが次のような場合:

Python

  1. resp = client.index(
  2. index="index",
  3. id="1",
  4. document={
  5. "designation": "spoon",
  6. "price": 13
  7. },
  8. )
  9. print(resp)

Ruby

  1. response = client.index(
  2. index: 'index',
  3. id: 1,
  4. body: {
  5. designation: 'spoon',
  6. price: 13
  7. }
  8. )
  9. puts response

Js

  1. const response = await client.index({
  2. index: "index",
  3. id: 1,
  4. document: {
  5. designation: "spoon",
  6. price: 13,
  7. },
  8. });
  9. console.log(response);

コンソール

  1. PUT index/_doc/1
  2. {
  3. "designation": "spoon",
  4. "price": 13
  5. }

そして、検索リクエストが次のような場合:

Python

  1. resp = client.search(
  2. index="index",
  3. aggs={
  4. "price_ranges": {
  5. "range": {
  6. "field": "price",
  7. "ranges": [
  8. {
  9. "to": 10
  10. },
  11. {
  12. "from": 10,
  13. "to": 100
  14. },
  15. {
  16. "from": 100
  17. }
  18. ]
  19. }
  20. }
  21. },
  22. )
  23. print(resp)

Ruby

  1. response = client.search(
  2. index: 'index',
  3. body: {
  4. aggregations: {
  5. price_ranges: {
  6. range: {
  7. field: 'price',
  8. ranges: [
  9. {
  10. to: 10
  11. },
  12. {
  13. from: 10,
  14. to: 100
  15. },
  16. {
  17. from: 100
  18. }
  19. ]
  20. }
  21. }
  22. }
  23. }
  24. )
  25. puts response

Js

  1. const response = await client.search({
  2. index: "index",
  3. aggs: {
  4. price_ranges: {
  5. range: {
  6. field: "price",
  7. ranges: [
  8. {
  9. to: 10,
  10. },
  11. {
  12. from: 10,
  13. to: 100,
  14. },
  15. {
  16. from: 100,
  17. },
  18. ],
  19. },
  20. },
  21. },
  22. });
  23. console.log(response);

コンソール

  1. GET index/_search
  2. {
  3. "aggs": {
  4. "price_ranges": {
  5. "range": {
  6. "field": "price",
  7. "ranges": [
  8. { "to": 10 },
  9. { "from": 10, "to": 100 },
  10. { "from": 100 }
  11. ]
  12. }
  13. }
  14. }
  15. }

この場合、ドキュメントはインデックス時にprice_rangeフィールドで強化され、これはkeywordとしてマッピングされるべきです:

Python

  1. resp = client.indices.create(
  2. index="index",
  3. mappings={
  4. "properties": {
  5. "price_range": {
  6. "type": "keyword"
  7. }
  8. }
  9. },
  10. )
  11. print(resp)
  12. resp1 = client.index(
  13. index="index",
  14. id="1",
  15. document={
  16. "designation": "spoon",
  17. "price": 13,
  18. "price_range": "10-100"
  19. },
  20. )
  21. print(resp1)

Ruby

  1. response = client.indices.create(
  2. index: 'index',
  3. body: {
  4. mappings: {
  5. properties: {
  6. price_range: {
  7. type: 'keyword'
  8. }
  9. }
  10. }
  11. }
  12. )
  13. puts response
  14. response = client.index(
  15. index: 'index',
  16. id: 1,
  17. body: {
  18. designation: 'spoon',
  19. price: 13,
  20. price_range: '10-100'
  21. }
  22. )
  23. puts response

Js

  1. const response = await client.indices.create({
  2. index: "index",
  3. mappings: {
  4. properties: {
  5. price_range: {
  6. type: "keyword",
  7. },
  8. },
  9. },
  10. });
  11. console.log(response);
  12. const response1 = await client.index({
  13. index: "index",
  14. id: 1,
  15. document: {
  16. designation: "spoon",
  17. price: 13,
  18. price_range: "10-100",
  19. },
  20. });
  21. console.log(response1);

コンソール

  1. PUT index
  2. {
  3. "mappings": {
  4. "properties": {
  5. "price_range": {
  6. "type": "keyword"
  7. }
  8. }
  9. }
  10. }
  11. PUT index/_doc/1
  12. {
  13. "designation": "spoon",
  14. "price": 13,
  15. "price_range": "10-100"
  16. }

そして、検索リクエストはこの新しいフィールドを集約することができ、priceフィールドで[range]集約を実行する必要はありません。

Python

  1. resp = client.search(
  2. index="index",
  3. aggs={
  4. "price_ranges": {
  5. "terms": {
  6. "field": "price_range"
  7. }
  8. }
  9. },
  10. )
  11. print(resp)

Ruby

  1. response = client.search(
  2. index: 'index',
  3. body: {
  4. aggregations: {
  5. price_ranges: {
  6. terms: {
  7. field: 'price_range'
  8. }
  9. }
  10. }
  11. }
  12. )
  13. puts response

Js

  1. const response = await client.search({
  2. index: "index",
  3. aggs: {
  4. price_ranges: {
  5. terms: {
  6. field: "price_range",
  7. },
  8. },
  9. },
  10. });
  11. console.log(response);

コンソール

  1. GET index/_search
  2. {
  3. "aggs": {
  4. "price_ranges": {
  5. "terms": {
  6. "field": "price_range"
  7. }
  8. }
  9. }
  10. }

識別子をキーワードとしてマッピングすることを検討する

すべての数値データをnumericフィールドデータ型としてマッピングする必要はありません。Elasticsearchは、[integerlongのような数値フィールドをrangeクエリ用に最適化します。しかし、keywordフィールドはtermや他のterm-levelクエリに適しています。

ISBNや製品IDのような識別子は、rangeクエリで使用されることはほとんどありません。しかし、これらはしばしばterm-levelクエリを使用して取得されます。

数値識別子をkeywordとしてマッピングすることを検討してください:

  • 識別子データをrangeクエリを使用して検索する予定がない場合。
  • 高速な取得が重要です。termクエリは、keywordフィールドでの検索がtermフィールドでの数値フィールドの検索よりも速いことがよくあります。

どちらを使用するか不明な場合は、multi-fieldを使用して、データをkeyword および 数値データ型としてマッピングできます。

スクリプトを避ける

可能であれば、scriptベースのソート、集約内のスクリプト、およびscript_scoreクエリの使用を避けてください。スクリプト、キャッシュ、および検索速度を参照してください。

丸めた日付を検索する

リードアヘッドを使用する日付フィールドに対するクエリは、マッチする範囲が常に変わるため、通常はキャッシュ可能ではありません。しかし、丸めた日付に切り替えることは、ユーザーエクスペリエンスの観点からしばしば受け入れられ、クエリキャッシュをより良く活用する利点があります。

たとえば、以下のクエリ:

Python

  1. resp = client.index(
  2. index="index",
  3. id="1",
  4. document={
  5. "my_date": "2016-05-11T16:30:55.328Z"
  6. },
  7. )
  8. print(resp)
  9. resp1 = client.search(
  10. index="index",
  11. query={
  12. "constant_score": {
  13. "filter": {
  14. "range": {
  15. "my_date": {
  16. "gte": "now-1h",
  17. "lte": "now"
  18. }
  19. }
  20. }
  21. }
  22. },
  23. )
  24. print(resp1)

Ruby

  1. response = client.index(
  2. index: 'index',
  3. id: 1,
  4. body: {
  5. my_date: '2016-05-11T16:30:55.328Z'
  6. }
  7. )
  8. puts response
  9. response = client.search(
  10. index: 'index',
  11. body: {
  12. query: {
  13. constant_score: {
  14. filter: {
  15. range: {
  16. my_date: {
  17. gte: 'now-1h',
  18. lte: 'now'
  19. }
  20. }
  21. }
  22. }
  23. }
  24. }
  25. )
  26. puts response

Js

  1. const response = await client.index({
  2. index: "index",
  3. id: 1,
  4. document: {
  5. my_date: "2016-05-11T16:30:55.328Z",
  6. },
  7. });
  8. console.log(response);
  9. const response1 = await client.search({
  10. index: "index",
  11. query: {
  12. constant_score: {
  13. filter: {
  14. range: {
  15. my_date: {
  16. gte: "now-1h",
  17. lte: "now",
  18. },
  19. },
  20. },
  21. },
  22. },
  23. });
  24. console.log(response1);

コンソール

  1. PUT index/_doc/1
  2. {
  3. "my_date": "2016-05-11T16:30:55.328Z"
  4. }
  5. GET index/_search
  6. {
  7. "query": {
  8. "constant_score": {
  9. "filter": {
  10. "range": {
  11. "my_date": {
  12. "gte": "now-1h",
  13. "lte": "now"
  14. }
  15. }
  16. }
  17. }
  18. }
  19. }

次のクエリに置き換えることができます:

Python

  1. resp = client.search(
  2. index="index",
  3. query={
  4. "constant_score": {
  5. "filter": {
  6. "range": {
  7. "my_date": {
  8. "gte": "now-1h/m",
  9. "lte": "now/m"
  10. }
  11. }
  12. }
  13. }
  14. },
  15. )
  16. print(resp)

Ruby

  1. response = client.search(
  2. index: 'index',
  3. body: {
  4. query: {
  5. constant_score: {
  6. filter: {
  7. range: {
  8. my_date: {
  9. gte: 'now-1h/m',
  10. lte: 'now/m'
  11. }
  12. }
  13. }
  14. }
  15. }
  16. }
  17. )
  18. puts response

Js

  1. const response = await client.search({
  2. index: "index",
  3. query: {
  4. constant_score: {
  5. filter: {
  6. range: {
  7. my_date: {
  8. gte: "now-1h/m",
  9. lte: "now/m",
  10. },
  11. },
  12. },
  13. },
  14. },
  15. });
  16. console.log(response);

コンソール

  1. GET index/_search
  2. {
  3. "query": {
  4. "constant_score": {
  5. "filter": {
  6. "range": {
  7. "my_date": {
  8. "gte": "now-1h/m",
  9. "lte": "now/m"
  10. }
  11. }
  12. }
  13. }
  14. }
  15. }

この場合、私たちは分を丸めました。したがって、現在の時間が16:31:29の場合、範囲クエリはmy_dateフィールドの値が15:31:0016:31:59の間にあるすべてにマッチします。そして、複数のユーザーが同じ分にこの範囲を含むクエリを実行すると、クエリキャッシュが少し速度を上げるのに役立つ可能性があります。丸めに使用される間隔が長いほど、クエリキャッシュがより多くの助けを提供できますが、あまりにも攻撃的な丸めはユーザーエクスペリエンスを損なう可能性があることに注意してください。

範囲を大きなキャッシュ可能な部分と小さなキャッシュ不可の部分に分割して、クエリキャッシュを活用しようとすることは魅力的かもしれませんが、以下のように:

Python

  1. resp = client.search(
  2. index="index",
  3. query={
  4. "constant_score": {
  5. "filter": {
  6. "bool": {
  7. "should": [
  8. {
  9. "range": {
  10. "my_date": {
  11. "gte": "now-1h",
  12. "lte": "now-1h/m"
  13. }
  14. }
  15. },
  16. {
  17. "range": {
  18. "my_date": {
  19. "gt": "now-1h/m",
  20. "lt": "now/m"
  21. }
  22. }
  23. },
  24. {
  25. "range": {
  26. "my_date": {
  27. "gte": "now/m",
  28. "lte": "now"
  29. }
  30. }
  31. }
  32. ]
  33. }
  34. }
  35. }
  36. },
  37. )
  38. print(resp)

Ruby

  1. response = client.search(
  2. index: 'index',
  3. body: {
  4. query: {
  5. constant_score: {
  6. filter: {
  7. bool: {
  8. should: [
  9. {
  10. range: {
  11. my_date: {
  12. gte: 'now-1h',
  13. lte: 'now-1h/m'
  14. }
  15. }
  16. },
  17. {
  18. range: {
  19. my_date: {
  20. gt: 'now-1h/m',
  21. lt: 'now/m'
  22. }
  23. }
  24. },
  25. {
  26. range: {
  27. my_date: {
  28. gte: 'now/m',
  29. lte: 'now'
  30. }
  31. }
  32. }
  33. ]
  34. }
  35. }
  36. }
  37. }
  38. }
  39. )
  40. puts response

Js

  1. const response = await client.search({
  2. index: "index",
  3. query: {
  4. constant_score: {
  5. filter: {
  6. bool: {
  7. should: [
  8. {
  9. range: {
  10. my_date: {
  11. gte: "now-1h",
  12. lte: "now-1h/m",
  13. },
  14. },
  15. },
  16. {
  17. range: {
  18. my_date: {
  19. gt: "now-1h/m",
  20. lt: "now/m",
  21. },
  22. },
  23. },
  24. {
  25. range: {
  26. my_date: {
  27. gte: "now/m",
  28. lte: "now",
  29. },
  30. },
  31. },
  32. ],
  33. },
  34. },
  35. },
  36. },
  37. });
  38. console.log(response);

コンソール

  1. GET index/_search
  2. {
  3. "query": {
  4. "constant_score": {
  5. "filter": {
  6. "bool": {
  7. "should": [
  8. {
  9. "range": {
  10. "my_date": {
  11. "gte": "now-1h",
  12. "lte": "now-1h/m"
  13. }
  14. }
  15. },
  16. {
  17. "range": {
  18. "my_date": {
  19. "gt": "now-1h/m",
  20. "lt": "now/m"
  21. }
  22. }
  23. },
  24. {
  25. "range": {
  26. "my_date": {
  27. "gte": "now/m",
  28. "lte": "now"
  29. }
  30. }
  31. }
  32. ]
  33. }
  34. }
  35. }
  36. }
  37. }

しかし、そのような実践は、boolクエリによって導入されるオーバーヘッドが、クエリキャッシュのより良い活用からの節約を打ち消す可能性があるため、場合によってはクエリを遅くする可能性があります。

読み取り専用インデックスの強制マージ

読み取り専用のインデックスは、単一セグメントにマージされることから利益を得る可能性があります。これは通常、時間ベースのインデックスに当てはまります:現在の時間枠のインデックスのみが新しいドキュメントを受け取っており、古いインデックスは読み取り専用です。強制的に単一セグメントにマージされたシャードは、検索を実行するためによりシンプルで効率的なデータ構造を使用できます。

まだ書き込みを行っているインデックスや、将来的に再度書き込む予定のインデックスを強制的にマージしないでください。代わりに、自動バックグラウンドマージプロセスに依存して、インデックスがスムーズに動作するように必要に応じてマージを実行させてください。強制的にマージされたインデックスに書き込みを続けると、そのパフォーマンスが大幅に悪化する可能性があります。

検索速度の調整

Python

  1. resp = client.indices.create(
  2. index="index",
  3. mappings={
  4. "properties": {
  5. "foo": {
  6. "type": "keyword",
  7. "eager_global_ordinals": True
  8. }
  9. }
  10. },
  11. )
  12. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'index',
  3. body: {
  4. mappings: {
  5. properties: {
  6. foo: {
  7. type: 'keyword',
  8. eager_global_ordinals: true
  9. }
  10. }
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.indices.create({
  2. index: "index",
  3. mappings: {
  4. properties: {
  5. foo: {
  6. type: "keyword",
  7. eager_global_ordinals: true,
  8. },
  9. },
  10. },
  11. });
  12. console.log(response);

コンソール

  1. PUT index
  2. {
  3. "mappings": {
  4. "properties": {
  5. "foo": {
  6. "type": "keyword",
  7. "eager_global_ordinals": true
  8. }
  9. }
  10. }
  11. }

ファイルシステムキャッシュをウォームアップする

Elasticsearchを実行しているマシンが再起動されると、ファイルシステムキャッシュは空になります。そのため、オペレーティングシステムがインデックスのホットな領域をメモリにロードするまでに時間がかかります。これにより、検索操作が迅速になります。オペレーティングシステムに、ファイル拡張子に応じてどのファイルを積極的にメモリにロードするかを明示的に指示することができます(index.store.preload設定を使用)。

あまりにも多くのインデックスやファイルに対してファイルシステムキャッシュにデータを積極的にロードすると、ファイルシステムキャッシュがすべてのデータを保持するのに十分大きくない場合、検索が遅くなります。注意して使用してください。

インデックスソートを使用して結合を高速化する

インデックスソートは、わずかに遅いインデックス化のコストで結合を高速化するのに役立ちます。詳細については、インデックスソートのドキュメントを参照してください。

キャッシュ利用を最適化するためにプレファレンスを使用する

検索パフォーマンスを向上させるために役立つ複数のキャッシュがあります。たとえば、ファイルシステムキャッシュリクエストキャッシュ、またはクエリキャッシュなどです。しかし、これらのキャッシュはすべてノードレベルで管理されているため、同じリクエストを連続して2回実行し、1つ以上のレプリカを持ち、ラウンドロビン(デフォルトのルーティングアルゴリズム)を使用すると、これらの2つのリクエストは異なるシャードコピーに送信され、ノードレベルのキャッシュが役立たなくなります。

検索アプリケーションのユーザーが、インデックスの狭いサブセットを分析するために、たとえば、似たようなリクエストを次々に実行することが一般的であるため、現在のユーザーまたはセッションを識別するプレファレンス値を使用することで、キャッシュの使用を最適化できる可能性があります。

レプリカはスループットに役立つかもしれませんが、常にそうではありません

レジリエンシーを改善するだけでなく、レプリカはスループットを改善するのにも役立ちます。たとえば、単一シャードインデックスと3つのノードがある場合、すべてのノードが利用されるようにするために、レプリカの数を2に設定する必要があります。

次に、2シャードインデックスと2ノードがあると仮定します。1つのケースでは、レプリカの数は0であり、各ノードが単一のシャードを保持します。2番目のケースでは、レプリカの数は1であり、各ノードが2つのシャードを持っています。検索パフォーマンスの観点から、どのセットアップが最もパフォーマンスが良いでしょうか?通常、ノードあたりのシャード数が少ないセットアップの方がパフォーマンスが良いです。その理由は、各シャードに対して利用可能なファイルシステムキャッシュの割合が大きくなり、ファイルシステムキャッシュがElasticsearchのパフォーマンス要因の1つである可能性が高いからです。同時に、レプリカを持たないセットアップは、単一ノードの障害が発生した場合に失敗の影響を受けるため、スループットと可用性の間にはトレードオフがあります。

では、適切なレプリカの数は何ですか?ノードがnum_nodesのクラスターがあり、num_primariesのプライマリシャードが合計であり、同時にmax_failuresノードの障害に対処できるようにしたい場合、適切なレプリカの数はmax(max_failures, ceil(num_nodes / num_primaries) - 1)です。

検索プロファイラーでクエリを調整する

プロファイルAPIは、クエリと集約の各コンポーネントがリクエストを処理するのにかかる時間にどのように影響するかについての詳細情報を提供します。

Kibanaの[https://www.elastic.co/guide/en/kibana/8.15/xpack-profiler.html]は、プロファイル結果を簡単にナビゲートして分析し、パフォーマンスを改善し、負荷を軽減するためにクエリを調整する方法についての洞察を提供します。

プロファイルAPI自体はクエリにかなりのオーバーヘッドを追加するため、この情報はさまざまなクエリコンポーネントの相対的なコストを理解するために最も適しています。実際の処理時間の信頼できる測定値を提供するものではありません。

index_phrasesを使用してフレーズクエリを高速化する

textフィールドには、2シングルをインデックス化するindex_phrasesオプションがあり、スロップのないフレーズクエリを実行するためにクエリパーサーによって自動的に活用されます。ユースケースに多くのフレーズクエリを実行することが含まれる場合、これによりクエリが大幅に高速化される可能性があります。

index_prefixesを使用してプレフィックスクエリを高速化する

textフィールドには、すべての用語のプレフィックスをインデックス化するindex_prefixesオプションがあり、プレフィックスクエリを実行するためにクエリパーサーによって自動的に活用されます。ユースケースに多くのプレフィックスクエリを実行することが含まれる場合、これによりクエリが大幅に高速化される可能性があります。

フィルタリングを高速化するためにconstant_keywordを使用する

フィルタのコストは、主に一致するドキュメントの数の関数であるという一般的なルールがあります。サイクルを含むインデックスがあると仮定します。自転車の数が多く、cycle_type: bicycleでフィルタを実行する多くの検索があります。この非常に一般的なフィルタは、残念ながらほとんどのドキュメントに一致するため、非常にコストがかかります。このフィルタを実行しない簡単な方法があります:自転車を独自のインデックスに移動し、このインデックスを検索することで自転車をフィルタリングします。

残念ながら、これによりクライアント側のロジックが複雑になる可能性がありますが、constant_keywordが役立ちます。自転車を含むインデックスでcycle_typeconstant_keywordとして値bicycleでマッピングすることで、クライアントはモノリシックインデックスで実行していたのと同じクエリを実行し続けることができ、Elasticsearchはcycle_typeのフィルタを無視して自転車インデックスで適切な処理を行います。値がbicycleの場合はヒットを返さず、そうでない場合はヒットを返します。

マッピングは次のようになります:

Python

  1. resp = client.indices.create(
  2. index="bicycles",
  3. mappings={
  4. "properties": {
  5. "cycle_type": {
  6. "type": "constant_keyword",
  7. "value": "bicycle"
  8. },
  9. "name": {
  10. "type": "text"
  11. }
  12. }
  13. },
  14. )
  15. print(resp)
  16. resp1 = client.indices.create(
  17. index="other_cycles",
  18. mappings={
  19. "properties": {
  20. "cycle_type": {
  21. "type": "keyword"
  22. },
  23. "name": {
  24. "type": "text"
  25. }
  26. }
  27. },
  28. )
  29. print(resp1)

Ruby

  1. response = client.indices.create(
  2. index: 'bicycles',
  3. body: {
  4. mappings: {
  5. properties: {
  6. cycle_type: {
  7. type: 'constant_keyword',
  8. value: 'bicycle'
  9. },
  10. name: {
  11. type: 'text'
  12. }
  13. }
  14. }
  15. }
  16. )
  17. puts response
  18. response = client.indices.create(
  19. index: 'other_cycles',
  20. body: {
  21. mappings: {
  22. properties: {
  23. cycle_type: {
  24. type: 'keyword'
  25. },
  26. name: {
  27. type: 'text'
  28. }
  29. }
  30. }
  31. }
  32. )
  33. puts response

Js

  1. const response = await client.indices.create({
  2. index: "bicycles",
  3. mappings: {
  4. properties: {
  5. cycle_type: {
  6. type: "constant_keyword",
  7. value: "bicycle",
  8. },
  9. name: {
  10. type: "text",
  11. },
  12. },
  13. },
  14. });
  15. console.log(response);
  16. const response1 = await client.indices.create({
  17. index: "other_cycles",
  18. mappings: {
  19. properties: {
  20. cycle_type: {
  21. type: "keyword",
  22. },
  23. name: {
  24. type: "text",
  25. },
  26. },
  27. },
  28. });
  29. console.log(response1);

コンソール

  1. PUT bicycles
  2. {
  3. "mappings": {
  4. "properties": {
  5. "cycle_type": {
  6. "type": "constant_keyword",
  7. "value": "bicycle"
  8. },
  9. "name": {
  10. "type": "text"
  11. }
  12. }
  13. }
  14. }
  15. PUT other_cycles
  16. {
  17. "mappings": {
  18. "properties": {
  19. "cycle_type": {
  20. "type": "keyword"
  21. },
  22. "name": {
  23. "type": "text"
  24. }
  25. }
  26. }
  27. }

インデックスを2つに分割しています:1つは自転車のみを含み、もう1つは他のサイクル(ユニサイクル、トライサイクルなど)を含みます。検索時には、両方のインデックスを検索する必要がありますが、クエリを変更する必要はありません。

Python

  1. resp = client.search(
  2. index="bicycles,other_cycles",
  3. query={
  4. "bool": {
  5. "must": {
  6. "match": {
  7. "description": "dutch"
  8. }
  9. },
  10. "filter": {
  11. "term": {
  12. "cycle_type": "bicycle"
  13. }
  14. }
  15. }
  16. },
  17. )
  18. print(resp)

Ruby

  1. response = client.search(
  2. index: 'bicycles,other_cycles',
  3. body: {
  4. query: {
  5. bool: {
  6. must: {
  7. match: {
  8. description: 'dutch'
  9. }
  10. },
  11. filter: {
  12. term: {
  13. cycle_type: 'bicycle'
  14. }
  15. }
  16. }
  17. }
  18. }
  19. )
  20. puts response

Js

  1. const response = await client.search({
  2. index: "bicycles,other_cycles",
  3. query: {
  4. bool: {
  5. must: {
  6. match: {
  7. description: "dutch",
  8. },
  9. },
  10. filter: {
  11. term: {
  12. cycle_type: "bicycle",
  13. },
  14. },
  15. },
  16. },
  17. });
  18. console.log(response);

コンソール

  1. GET bicycles,other_cycles/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": {
  6. "match": {
  7. "description": "dutch"
  8. }
  9. },
  10. "filter": {
  11. "term": {
  12. "cycle_type": "bicycle"
  13. }
  14. }
  15. }
  16. }
  17. }
  1. #### Python
  2. ``````python
  3. resp = client.search(
  4. index="bicycles,other_cycles",
  5. query={
  6. "match": {
  7. "description": "dutch"
  8. }
  9. },
  10. )
  11. print(resp)
  12. `

Ruby

  1. response = client.search(
  2. index: 'bicycles,other_cycles',
  3. body: {
  4. query: {
  5. match: {
  6. description: 'dutch'
  7. }
  8. }
  9. }
  10. )
  11. puts response

Js

  1. const response = await client.search({
  2. index: "bicycles,other_cycles",
  3. query: {
  4. match: {
  5. description: "dutch",
  6. },
  7. },
  8. });
  9. console.log(response);

コンソール

  1. GET bicycles,other_cycles/_search
  2. {
  3. "query": {
  4. "match": {
  5. "description": "dutch"
  6. }
  7. }
  8. }

other_cyclesインデックスでは、Elasticsearchはbicyclecycle_typeフィールドの用語辞書に存在しないことをすぐに把握し、ヒットなしの検索応答を返します。

これは、一般的な値を専用のインデックスに配置することでクエリを安価にする強力な方法です。このアイデアは、複数のフィールドにわたって組み合わせることもできます。たとえば、各サイクルの色を追跡し、bicyclesインデックスが黒い自転車の大多数を持つ場合、bicycles-blackbicycles-other-colorsインデックスに分割することができます。

この最適化にはconstant_keywordは厳密には必要ありません。フィルタに基づいて関連するインデックスにクエリをルーティングするためにクライアント側のロジックを更新することも可能です。しかし、constant_keywordはそれを透過的にし、検索リクエストをインデックスのトポロジーから切り離すことを可能にし、非常に少ないオーバーヘッドで実現します。