検索結果のページネーション

デフォルトでは、検索は最初の10件の一致するヒットを返します。より大きな結果セットをページングするには、search APIfromおよびsizeパラメータを使用できます。fromパラメータはスキップするヒットの数を定義し、デフォルトは0です。sizeパラメータは返す最大ヒット数です。これら2つのパラメータを組み合わせることで、結果のページを定義します。

Python

  1. resp = client.search(
  2. from_=5,
  3. size=20,
  4. query={
  5. "match": {
  6. "user.id": "kimchy"
  7. }
  8. },
  9. )
  10. print(resp)

Ruby

  1. response = client.search(
  2. body: {
  3. from: 5,
  4. size: 20,
  5. query: {
  6. match: {
  7. 'user.id' => 'kimchy'
  8. }
  9. }
  10. }
  11. )
  12. puts response

Go

  1. res, err := es.Search(
  2. es.Search.WithBody(strings.NewReader(`{
  3. "from": 5,
  4. "size": 20,
  5. "query": {
  6. "match": {
  7. "user.id": "kimchy"
  8. }
  9. }
  10. }`)),
  11. es.Search.WithPretty(),
  12. )
  13. fmt.Println(res, err)

Js

  1. const response = await client.search({
  2. from: 5,
  3. size: 20,
  4. query: {
  5. match: {
  6. "user.id": "kimchy",
  7. },
  8. },
  9. });
  10. console.log(response);

コンソール

  1. GET /_search
  2. {
  3. "from": 5,
  4. "size": 20,
  5. "query": {
  6. "match": {
  7. "user.id": "kimchy"
  8. }
  9. }
  10. }
  1. デフォルトでは、`````from`````および`````size`````を使用して10,000件以上のヒットをページングすることはできません。この制限は、[`````index.max_result_window`````](0dd33dc5c1f7a36a.md#index-max-result-window)インデックス設定によって設定された保護策です。10,000件以上のヒットをページングする必要がある場合は、代わりに[`````search_after`````](238327401f76c2ac.md#search-after)パラメータを使用してください。
  2. Elasticsearchは、Luceneの内部ドキュメントIDをタイブレーカーとして使用します。これらの内部ドキュメントIDは、同じデータのレプリカ間で完全に異なる場合があります。検索ヒットをページングする際、同じソート値を持つドキュメントが一貫して順序付けられないことがあります。
  3. ## 検索後
  4. `````search_after`````パラメータを使用して、前のページからの一連の[ソート値](/read/elasticsearch-8-15/edefedafdd88134e.md)を使用して次のページのヒットを取得できます。
  5. `````search_after`````を使用するには、同じ`````query`````および`````sort`````値を持つ複数の検索リクエストが必要です。最初のステップは、初期リクエストを実行することです。次の例では、結果を2つのフィールド(`````date`````および`````tie_breaker_id`````)でソートします:
  6. #### Python
  7. ``````python
  8. resp = client.search(
  9. index="twitter",
  10. query={
  11. "match": {
  12. "title": "elasticsearch"
  13. }
  14. },
  15. sort=[
  16. {
  17. "date": "asc"
  18. },
  19. {
  20. "tie_breaker_id": "asc"
  21. }
  22. ],
  23. )
  24. print(resp)
  25. `

Ruby

  1. response = client.search(
  2. index: 'twitter',
  3. body: {
  4. query: {
  5. match: {
  6. title: 'elasticsearch'
  7. }
  8. },
  9. sort: [
  10. {
  11. date: 'asc'
  12. },
  13. {
  14. tie_breaker_id: 'asc'
  15. }
  16. ]
  17. }
  18. )
  19. puts response

Js

  1. const response = await client.search({
  2. index: "twitter",
  3. query: {
  4. match: {
  5. title: "elasticsearch",
  6. },
  7. },
  8. sort: [
  9. {
  10. date: "asc",
  11. },
  12. {
  13. tie_breaker_id: "asc",
  14. },
  15. ],
  16. });
  17. console.log(response);

コンソール

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "title": "elasticsearch"
  6. }
  7. },
  8. "sort": [
  9. {"date": "asc"},
  10. {"tie_breaker_id": "asc"}
  11. ]
  12. }
_idフィールドのコピー、doc_valuesが有効になっています

検索応答には、各ヒットのsort値の配列が含まれます:

コンソール-結果

  1. {
  2. "took" : 17,
  3. "timed_out" : false,
  4. "_shards" : ...,
  5. "hits" : {
  6. "total" : ...,
  7. "max_score" : null,
  8. "hits" : [
  9. ...
  10. {
  11. "_index" : "twitter",
  12. "_id" : "654322",
  13. "_score" : null,
  14. "_source" : ...,
  15. "sort" : [
  16. 1463538855,
  17. "654322"
  18. ]
  19. },
  20. {
  21. "_index" : "twitter",
  22. "_id" : "654323",
  23. "_score" : null,
  24. "_source" : ...,
  25. "sort" : [
  26. 1463538857,
  27. "654323"
  28. ]
  29. }
  30. ]
  31. }
  32. }
最後に返されたヒットのソート値。

次のページの結果を取得するには、リクエストを繰り返し、最後のヒットからsort値を取得し、それをsearch_after配列に挿入します:

Python

  1. resp = client.search(
  2. index="twitter",
  3. query={
  4. "match": {
  5. "title": "elasticsearch"
  6. }
  7. },
  8. search_after=[
  9. 1463538857,
  10. "654323"
  11. ],
  12. sort=[
  13. {
  14. "date": "asc"
  15. },
  16. {
  17. "tie_breaker_id": "asc"
  18. }
  19. ],
  20. )
  21. print(resp)

Ruby

  1. response = client.search(
  2. index: 'twitter',
  3. body: {
  4. query: {
  5. match: {
  6. title: 'elasticsearch'
  7. }
  8. },
  9. search_after: [
  10. 1_463_538_857,
  11. '654323'
  12. ],
  13. sort: [
  14. {
  15. date: 'asc'
  16. },
  17. {
  18. tie_breaker_id: 'asc'
  19. }
  20. ]
  21. }
  22. )
  23. puts response

Js

  1. const response = await client.search({
  2. index: "twitter",
  3. query: {
  4. match: {
  5. title: "elasticsearch",
  6. },
  7. },
  8. search_after: [1463538857, "654323"],
  9. sort: [
  10. {
  11. date: "asc",
  12. },
  13. {
  14. tie_breaker_id: "asc",
  15. },
  16. ],
  17. });
  18. console.log(response);

コンソール

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "title": "elasticsearch"
  6. }
  7. },
  8. "search_after": [1463538857, "654323"],
  9. "sort": [
  10. {"date": "asc"},
  11. {"tie_breaker_id": "asc"}
  12. ]
  13. }

このプロセスを繰り返し、毎回新しいページの結果を取得するたびにsearch_after配列を更新します。これらのリクエストの間にリフレッシュが発生すると、結果の順序が変わる可能性があり、ページ間で一貫性のない結果を引き起こす可能性があります。これを防ぐために、検索中に現在のインデックス状態を保持するためにポイントインタイム(PIT)を作成できます。

Python

  1. resp = client.open_point_in_time(
  2. index="my-index-000001",
  3. keep_alive="1m",
  4. )
  5. print(resp)

Ruby

  1. response = client.open_point_in_time(
  2. index: 'my-index-000001',
  3. keep_alive: '1m'
  4. )
  5. puts response

Js

  1. const response = await client.openPointInTime({
  2. index: "my-index-000001",
  3. keep_alive: "1m",
  4. });
  5. console.log(response);

コンソール

  1. POST /my-index-000001/_pit?keep_alive=1m

APIはPIT IDを返します。

コンソール-結果

  1. {
  2. "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
  3. }

最初のページの結果を取得するには、sort引数を持つ検索リクエストを送信します。PITを使用する場合は、pit.idパラメータにPIT IDを指定し、リクエストパスからターゲットデータストリームまたはインデックスを省略します。

すべてのPIT検索リクエストは、_shard_docと呼ばれる暗黙のソートタイブレーカーフィールドを追加します。これは明示的に提供することもできます。PITを使用できない場合は、sortにタイブレーカーフィールドを含めることをお勧めします。このタイブレーカーフィールドは、各ドキュメントに対して一意の値を含む必要があります。タイブレーカーフィールドを含めない場合、ページングされた結果がヒットを見逃したり重複したりする可能性があります。

検索後リクエストには、ソート順が_shard_docで、総ヒット数が追跡されていない場合に、より高速にするための最適化があります。順序に関係なくすべてのドキュメントを反復処理したい場合、これが最も効率的なオプションです。

  1. #### Python
  2. ``````python
  3. resp = client.search(
  4. size=10000,
  5. query={
  6. "match": {
  7. "user.id": "elkbee"
  8. }
  9. },
  10. pit={
  11. "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  12. "keep_alive": "1m"
  13. },
  14. sort=[
  15. {
  16. "@timestamp": {
  17. "order": "asc",
  18. "format": "strict_date_optional_time_nanos",
  19. "numeric_type": "date_nanos"
  20. }
  21. }
  22. ],
  23. )
  24. print(resp)
  25. `

Js

  1. const response = await client.search({
  2. size: 10000,
  3. query: {
  4. match: {
  5. "user.id": "elkbee",
  6. },
  7. },
  8. pit: {
  9. id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  10. keep_alive: "1m",
  11. },
  12. sort: [
  13. {
  14. "@timestamp": {
  15. order: "asc",
  16. format: "strict_date_optional_time_nanos",
  17. numeric_type: "date_nanos",
  18. },
  19. },
  20. ],
  21. });
  22. console.log(response);

コンソール

  1. GET /_search
  2. {
  3. "size": 10000,
  4. "query": {
  5. "match" : {
  6. "user.id" : "elkbee"
  7. }
  8. },
  9. "pit": {
  10. "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  11. "keep_alive": "1m"
  12. },
  13. "sort": [
  14. {"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos", "numeric_type" : "date_nanos" }}
  15. ]
  16. }
検索のためのPIT ID。
_shard_doc昇順での検索のためのヒットをソートします。

検索応答には、各ヒットのsort値の配列が含まれます。PITを使用した場合、タイブレーカーは各ヒットの最後のsort値として含まれます。このタイブレーカーは、PITを使用するすべての検索リクエストで自動的に追加されます。_shard_doc値は、PIT内のシャードインデックスとLuceneの内部ドキュメントIDの組み合わせであり、各ドキュメントごとに一意で、PIT内で一定です。検索リクエストでタイブレーカーを明示的に追加して順序をカスタマイズすることもできます:

Python

  1. resp = client.search(
  2. size=10000,
  3. query={
  4. "match": {
  5. "user.id": "elkbee"
  6. }
  7. },
  8. pit={
  9. "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  10. "keep_alive": "1m"
  11. },
  12. sort=[
  13. {
  14. "@timestamp": {
  15. "order": "asc",
  16. "format": "strict_date_optional_time_nanos"
  17. }
  18. },
  19. {
  20. "_shard_doc": "desc"
  21. }
  22. ],
  23. )
  24. print(resp)

Js

  1. const response = await client.search({
  2. size: 10000,
  3. query: {
  4. match: {
  5. "user.id": "elkbee",
  6. },
  7. },
  8. pit: {
  9. id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  10. keep_alive: "1m",
  11. },
  12. sort: [
  13. {
  14. "@timestamp": {
  15. order: "asc",
  16. format: "strict_date_optional_time_nanos",
  17. },
  18. },
  19. {
  20. _shard_doc: "desc",
  21. },
  22. ],
  23. });
  24. console.log(response);

コンソール

  1. GET /_search
  2. {
  3. "size": 10000,
  4. "query": {
  5. "match" : {
  6. "user.id" : "elkbee"
  7. }
  8. },
  9. "pit": {
  10. "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  11. "keep_alive": "1m"
  12. },
  13. "sort": [
  14. {"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos"}},
  15. {"_shard_doc": "desc"}
  16. ]
  17. }
検索のためのPIT ID。
_shard_doc降順での検索のためのヒットをソートします。

コンソール-結果

  1. {
  2. "pit_id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  3. "took" : 17,
  4. "timed_out" : false,
  5. "_shards" : ...,
  6. "hits" : {
  7. "total" : ...,
  8. "max_score" : null,
  9. "hits" : [
  10. ...
  11. {
  12. "_index" : "my-index-000001",
  13. "_id" : "FaslK3QBySSL_rrj9zM5",
  14. "_score" : null,
  15. "_source" : ...,
  16. "sort" : [
  17. "2021-05-20T05:30:04.832Z",
  18. 4294967298
  19. ]
  20. }
  21. ]
  22. }
  23. }
時間のポイントのために更新されたid
最後に返されたヒットのソート値。
pit_id内の各ドキュメントごとに一意のタイブレーカー値。

次のページの結果を取得するには、前の検索を再実行し、最後のヒットのソート値(タイブレーカーを含む)をsearch_after引数として使用します。PITを使用する場合は、pit.idパラメータに最新のPIT IDを使用します。検索のqueryおよびsort引数は変更されるべきではありません。提供されている場合、from引数は0(デフォルト)または-1でなければなりません。

Python

  1. resp = client.search(
  2. size=10000,
  3. query={
  4. "match": {
  5. "user.id": "elkbee"
  6. }
  7. },
  8. pit={
  9. "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  10. "keep_alive": "1m"
  11. },
  12. sort=[
  13. {
  14. "@timestamp": {
  15. "order": "asc",
  16. "format": "strict_date_optional_time_nanos"
  17. }
  18. }
  19. ],
  20. search_after=[
  21. "2021-05-20T05:30:04.832Z",
  22. 4294967298
  23. ],
  24. track_total_hits=False,
  25. )
  26. print(resp)

Js

  1. const response = await client.search({
  2. size: 10000,
  3. query: {
  4. match: {
  5. "user.id": "elkbee",
  6. },
  7. },
  8. pit: {
  9. id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  10. keep_alive: "1m",
  11. },
  12. sort: [
  13. {
  14. "@timestamp": {
  15. order: "asc",
  16. format: "strict_date_optional_time_nanos",
  17. },
  18. },
  19. ],
  20. search_after: ["2021-05-20T05:30:04.832Z", 4294967298],
  21. track_total_hits: false,
  22. });
  23. console.log(response);

コンソール

  1. GET /_search
  2. {
  3. "size": 10000,
  4. "query": {
  5. "match" : {
  6. "user.id" : "elkbee"
  7. }
  8. },
  9. "pit": {
  10. "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  11. "keep_alive": "1m"
  12. },
  13. "sort": [
  14. {"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos"}}
  15. ],
  16. "search_after": [
  17. "2021-05-20T05:30:04.832Z",
  18. 4294967298
  19. ],
  20. "track_total_hits": false
  21. }
前の検索によって返されたPIT ID。
前の検索の最後のヒットからのソート値。
ページングを高速化するために総ヒット数の追跡を無効にします。

このプロセスを繰り返して、追加のページの結果を取得できます。PITを使用する場合は、各検索リクエストのkeep_aliveパラメータを使用してPITの保持期間を延長できます。

終了したら、PITを削除する必要があります。

Python

  1. resp = client.close_point_in_time(
  2. id="46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  3. )
  4. print(resp)

Ruby

  1. response = client.close_point_in_time(
  2. body: {
  3. id: '46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=='
  4. }
  5. )
  6. puts response

Js

  1. const response = await client.closePointInTime({
  2. id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  3. });
  4. console.log(response);

コンソール

  1. DELETE /_pit
  2. {
  3. "id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
  4. }

スクロール検索結果

深いページネーションのためにスクロールAPIの使用はもはや推奨されていません。10,000件以上のヒットをページングする際にインデックス状態を保持する必要がある場合は、search_afterパラメータをポイントインタイム(PIT)とともに使用してください。

searchリクエストは単一の「ページ」の結果を返しますが、scroll APIは、従来のデータベースでカーソルを使用するのと同じように、単一の検索リクエストから大量の結果(またはすべての結果)を取得するために使用できます。

スクロールはリアルタイムのユーザーリクエストを意図したものではなく、大量のデータを処理するためのものです。たとえば、1つのデータストリームまたはインデックスの内容を新しいデータストリームまたはインデックスに再インデックスするために使用されます。

スクロールと再インデックスのクライアントサポート

公式にサポートされているクライアントのいくつかは、スクロール検索と再インデックスを支援するヘルパーを提供します:

スクロールリクエストから返される結果は、初期searchリクエストが行われた時点でのデータストリームまたはインデックスの状態を反映しています。ドキュメントへのその後の変更(インデックス、更新、または削除)は、後の検索リクエストにのみ影響します。

スクロールを使用するには、初期検索リクエストでscrollパラメータをクエリ文字列に指定する必要があります。これにより、Elasticsearchは「検索コンテキスト」をどれくらいの間保持する必要があるかを指示します(検索コンテキストを保持するを参照)。例:?scroll=1m

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. scroll="1m",
  4. size=100,
  5. query={
  6. "match": {
  7. "message": "foo"
  8. }
  9. },
  10. )
  11. print(resp)

Ruby

  1. response = client.search(
  2. index: 'my-index-000001',
  3. scroll: '1m',
  4. body: {
  5. size: 100,
  6. query: {
  7. match: {
  8. message: 'foo'
  9. }
  10. }
  11. }
  12. )
  13. puts response

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. scroll: "1m",
  4. size: 100,
  5. query: {
  6. match: {
  7. message: "foo",
  8. },
  9. },
  10. });
  11. console.log(response);

コンソール

  1. POST /my-index-000001/_search?scroll=1m
  2. {
  3. "size": 100,
  4. "query": {
  5. "match": {
  6. "message": "foo"
  7. }
  8. }
  9. }

上記のリクエストからの結果には、次のバッチの結果を取得するために_scroll_idscroll APIに渡す必要があります。

Python

  1. resp = client.scroll(
  2. scroll="1m",
  3. scroll_id="DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  4. )
  5. print(resp)

Go

  1. res, err := es.Scroll(
  2. es.Scroll.WithBody(strings.NewReader(`{
  3. "scroll": "1m",
  4. "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
  5. }`)),
  6. es.Scroll.WithPretty(),
  7. )
  8. fmt.Println(res, err)

Js

  1. const response = await client.scroll({
  2. scroll: "1m",
  3. scroll_id: "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  4. });
  5. console.log(response);

コンソール

  1. POST /_search/scroll
  2. {
  3. "scroll" : "1m",
  4. "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
  5. }
GETまたはPOSTを使用でき、URLにはindex
名前—これは元のsearchリクエストで指定されます。
scrollパラメータは、Elasticsearchに検索コンテキストを1mの間オープンに保つよう指示します。
scroll_idパラメータ

sizeパラメータを使用すると、各バッチの結果で返される最大ヒット数を構成できます。scroll APIへの各呼び出しは、結果が返されるまで次のバッチを返します。つまり、hits配列が空になります。

初期検索リクエストと各後続のスクロールリクエストはそれぞれ_scroll_idを返します。_scroll_idはリクエスト間で変更される可能性がありますが、常に変更されるわけではありません。いずれにせよ、最近受信した_scroll_idのみを使用する必要があります。

リクエストが集約を指定している場合、初期検索応答のみが集約結果を含みます。

スクロールリクエストには、ソート順が_docの場合にそれを高速化する最適化があります。順序に関係なくすべてのドキュメントを反復処理したい場合、これが最も効率的なオプションです:

Php

  1. $params = [
  2. 'body' => [
  3. 'sort' => [
  4. '_doc',
  5. ],
  6. ],
  7. ];
  8. $response = $client->search($params);

Python

  1. resp = client.search(
  2. scroll="1m",
  3. sort=[
  4. "_doc"
  5. ],
  6. )
  7. print(resp)

Ruby

  1. response = client.search(
  2. scroll: '1m',
  3. body: {
  4. sort: [
  5. '_doc'
  6. ]
  7. }
  8. )
  9. puts response

Go

  1. res, err := es.Search(
  2. es.Search.WithBody(strings.NewReader(`{
  3. "sort": [
  4. "_doc"
  5. ]
  6. }`)),
  7. es.Search.WithScroll(time.Duration(60000000000)),
  8. es.Search.WithPretty(),
  9. )
  10. fmt.Println(res, err)

Js

  1. const response = await client.search({
  2. scroll: "1m",
  3. sort: ["_doc"],
  4. });
  5. console.log(response);

コンソール

  1. GET /_search?scroll=1m
  2. {
  3. "sort": [
  4. "_doc"
  5. ]
  6. }

検索コンテキストを保持する

スクロールは、初期検索リクエストの時点で一致したすべてのドキュメントを返します。これらのドキュメントへのその後の変更は無視されます。scroll_idは、Elasticsearchが正しいドキュメントを返すために必要なすべてを追跡する検索コンテキストを識別します。検索コンテキストは初期リクエストによって作成され、その後のリクエストによって保持されます。

  1. 通常、バックグラウンドマージプロセスは、インデックスを最適化するために小さなセグメントをマージして新しい大きなセグメントを作成します。小さなセグメントがもはや必要ない場合、それらは削除されます。このプロセスはスクロール中も続きますが、オープンな検索コンテキストは古いセグメントが削除されるのを防ぎます。なぜなら、それらはまだ使用中だからです。
  2. 古いセグメントを保持することは、より多くのディスクスペースとファイルハンドルが必要であることを意味します。ノードが十分な空きファイルハンドルを持つように構成されていることを確認してください。[ファイル記述子](/read/elasticsearch-8-15/cf63fb43f29c7f55.md)を参照してください。
  3. さらに、セグメントに削除または更新されたドキュメントが含まれている場合、検索コンテキストは、初期検索リクエストの時点でセグメント内の各ドキュメントがライブであったかどうかを追跡する必要があります。進行中の削除または更新があるインデックスで多くのオープンスクロールがある場合、ノードに十分なヒープスペースがあることを確認してください。
  4. オープンスクロールが多すぎることによって引き起こされる問題を防ぐために、ユーザーは特定の制限を超えてスクロールを開くことは許可されていません。デフォルトでは、オープンスクロールの最大数は500です。この制限は、`````search.max_open_scroll_context`````クラスター設定で更新できます。
  5. オープンな検索コンテキストがいくつあるかを確認するには、[ノード統計API](/read/elasticsearch-8-15/b347631483fdb07e.md)を使用してください。
  6. #### Php
  7. ``````php
  8. $params = [
  9. 'metric' => 'indices',
  10. 'index_metric' => 'search',
  11. ];
  12. $response = $client->nodes()->stats($params);
  13. `

Python

  1. resp = client.nodes.stats(
  2. metric="indices",
  3. index_metric="search",
  4. )
  5. print(resp)

Ruby

  1. response = client.nodes.stats(
  2. metric: 'indices',
  3. index_metric: 'search'
  4. )
  5. puts response

Go

  1. res, err := es.Nodes.Stats(
  2. es.Nodes.Stats.WithMetric([]string{"indices"}...),
  3. es.Nodes.Stats.WithIndexMetric([]string{"search"}...),
  4. )
  5. fmt.Println(res, err)

Js

  1. const response = await client.nodes.stats({
  2. metric: "indices",
  3. index_metric: "search",
  4. });
  5. console.log(response);

コンソール

  1. GET /_nodes/stats/indices/search

スクロールのクリア

検索コンテキストは、scrollタイムアウトが超えたときに自動的に削除されます。ただし、スクロールをオープンに保つことにはコストがかかります。前のセクションで説明したように、スクロールはもはや使用されていない場合は、clear-scroll APIを使用して明示的にクリアする必要があります。

Python

  1. resp = client.clear_scroll(
  2. scroll_id="DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  3. )
  4. print(resp)

Ruby

  1. response = client.clear_scroll(
  2. body: {
  3. scroll_id: 'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=='
  4. }
  5. )
  6. puts response

Go

  1. res, err := es.ClearScroll(
  2. es.ClearScroll.WithBody(strings.NewReader(`{
  3. "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
  4. }`)),
  5. )
  6. fmt.Println(res, err)

Js

  1. const response = await client.clearScroll({
  2. scroll_id: "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  3. });
  4. console.log(response);

コンソール

  1. DELETE /_search/scroll
  2. {
  3. "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
  4. }

複数のスクロールIDを配列として渡すことができます:

Python

  1. resp = client.clear_scroll(
  2. scroll_id=[
  3. "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  4. "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
  5. ],
  6. )
  7. print(resp)

Ruby

  1. response = client.clear_scroll(
  2. body: {
  3. scroll_id: [
  4. 'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==',
  5. 'DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB'
  6. ]
  7. }
  8. )
  9. puts response

Go

  1. res, err := es.ClearScroll(
  2. es.ClearScroll.WithBody(strings.NewReader(`{
  3. "scroll_id": [
  4. "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  5. "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
  6. ]
  7. }`)),
  8. )
  9. fmt.Println(res, err)

Js

  1. const response = await client.clearScroll({
  2. scroll_id: [
  3. "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  4. "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB",
  5. ],
  6. });
  7. console.log(response);

コンソール

  1. DELETE /_search/scroll
  2. {
  3. "scroll_id" : [
  4. "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
  5. "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
  6. ]
  7. }

すべての検索コンテキストは、_allパラメータでクリアできます:

Php

  1. $params = [
  2. 'scroll_id' => '_all',
  3. ];
  4. $response = $client->clearScroll($params);

Python

  1. resp = client.clear_scroll(
  2. scroll_id="_all",
  3. )
  4. print(resp)

Ruby

  1. response = client.clear_scroll(
  2. scroll_id: '_all'
  3. )
  4. puts response

Go

  1. res, err := es.ClearScroll(
  2. es.ClearScroll.WithScrollID("_all"),
  3. )
  4. fmt.Println(res, err)

Js

  1. const response = await client.clearScroll({
  2. scroll_id: "_all",
  3. });
  4. console.log(response);

コンソール

  1. DELETE /_search/scroll/_all
  1. #### Php
  2. ``````php
  3. $params = [
  4. 'scroll_id' => 'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB',
  5. ];
  6. $response = $client->clearScroll($params);
  7. `

Python

  1. resp = client.clear_scroll(
  2. scroll_id="DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB",
  3. )
  4. print(resp)

Ruby

  1. response = client.clear_scroll(
  2. scroll_id: 'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB'
  3. )
  4. puts response

Go

  1. res, err := es.ClearScroll(
  2. es.ClearScroll.WithScrollID("DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==", "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"),
  3. )
  4. fmt.Println(res, err)

Js

  1. const response = await client.clearScroll({
  2. scroll_id:
  3. "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB",
  4. });
  5. console.log(response);

コンソール

  1. DELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB

スライスされたスクロール

大量のドキュメントをページングする際、検索を複数のスライスに分割して独立して消費することが役立ちます:

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. scroll="1m",
  4. slice={
  5. "id": 0,
  6. "max": 2
  7. },
  8. query={
  9. "match": {
  10. "message": "foo"
  11. }
  12. },
  13. )
  14. print(resp)
  15. resp1 = client.search(
  16. index="my-index-000001",
  17. scroll="1m",
  18. slice={
  19. "id": 1,
  20. "max": 2
  21. },
  22. query={
  23. "match": {
  24. "message": "foo"
  25. }
  26. },
  27. )
  28. print(resp1)

Ruby

  1. response = client.search(
  2. index: 'my-index-000001',
  3. scroll: '1m',
  4. body: {
  5. slice: {
  6. id: 0,
  7. max: 2
  8. },
  9. query: {
  10. match: {
  11. message: 'foo'
  12. }
  13. }
  14. }
  15. )
  16. puts response
  17. response = client.search(
  18. index: 'my-index-000001',
  19. scroll: '1m',
  20. body: {
  21. slice: {
  22. id: 1,
  23. max: 2
  24. },
  25. query: {
  26. match: {
  27. message: 'foo'
  28. }
  29. }
  30. }
  31. )
  32. puts response

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. scroll: "1m",
  4. slice: {
  5. id: 0,
  6. max: 2,
  7. },
  8. query: {
  9. match: {
  10. message: "foo",
  11. },
  12. },
  13. });
  14. console.log(response);
  15. const response1 = await client.search({
  16. index: "my-index-000001",
  17. scroll: "1m",
  18. slice: {
  19. id: 1,
  20. max: 2,
  21. },
  22. query: {
  23. match: {
  24. message: "foo",
  25. },
  26. },
  27. });
  28. console.log(response1);

コンソール

  1. GET /my-index-000001/_search?scroll=1m
  2. {
  3. "slice": {
  4. "id": 0,
  5. "max": 2
  6. },
  7. "query": {
  8. "match": {
  9. "message": "foo"
  10. }
  11. }
  12. }
  13. GET /my-index-000001/_search?scroll=1m
  14. {
  15. "slice": {
  16. "id": 1,
  17. "max": 2
  18. },
  19. "query": {
  20. "match": {
  21. "message": "foo"
  22. }
  23. }
  24. }
スライスのID
スライスの最大数

最初のリクエストからの結果は、最初のスライス(ID:0)に属するドキュメントを返し、2番目のリクエストからの結果は、2番目のスライスに属するドキュメントを返します。最大スライス数が2に設定されているため、2つのリクエストの結果の和集合は、スライスなしのスクロールクエリの結果に相当します。デフォルトでは、分割は最初にシャードで行われ、その後各シャード内で_idフィールドを使用してローカルに行われます。ローカル分割はslice(doc) = floorMod(hashCode(doc._id), max))の式に従います。

各スクロールは独立しており、他のスクロールリクエストと同様に並行して処理できます。

スライスの数がシャードの数よりも大きい場合、スライスフィルターは最初の呼び出しで非常に遅く、O(N)の複雑さを持ち、メモリコストはスライスごとにNビットになります。Nはシャード内のドキュメントの総数です。数回の呼び出しの後、フィルターはキャッシュされ、後続の呼び出しはより速くなりますが、メモリの爆発を避けるために、並行して実行するスライスクエリの数を制限する必要があります。

ポイントインタイム APIは、より効率的なパーティショニング戦略をサポートし、この問題に悩まされることはありません。可能な場合は、スクロールの代わりにスライスを使用したポイントインタイム検索を使用することをお勧めします。

この高コストを回避する別の方法は、別のフィールドのdoc_valuesを使用してスライスを行うことです。フィールドは次の特性を持っている必要があります:

  • フィールドは数値です。
  • doc_valuesがそのフィールドで有効になっています
  • 各ドキュメントは単一の値を含む必要があります。指定されたフィールドに複数の値がある場合、最初の値が使用されます。
  • 各ドキュメントの値は、ドキュメントが作成されるときに1回設定され、決して更新されない必要があります。これにより、各スライスが決定論的な結果を得ることが保証されます。
  • フィールドの基数は高い必要があります。これにより、各スライスがほぼ同じ数のドキュメントを取得できます。

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. scroll="1m",
  4. slice={
  5. "field": "@timestamp",
  6. "id": 0,
  7. "max": 10
  8. },
  9. query={
  10. "match": {
  11. "message": "foo"
  12. }
  13. },
  14. )
  15. print(resp)

Ruby

  1. response = client.search(
  2. index: 'my-index-000001',
  3. scroll: '1m',
  4. body: {
  5. slice: {
  6. field: '@timestamp',
  7. id: 0,
  8. max: 10
  9. },
  10. query: {
  11. match: {
  12. message: 'foo'
  13. }
  14. }
  15. }
  16. )
  17. puts response

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. scroll: "1m",
  4. slice: {
  5. field: "@timestamp",
  6. id: 0,
  7. max: 10,
  8. },
  9. query: {
  10. match: {
  11. message: "foo",
  12. },
  13. },
  14. });
  15. console.log(response);

コンソール

  1. GET /my-index-000001/_search?scroll=1m
  2. {
  3. "slice": {
  4. "field": "@timestamp",
  5. "id": 0,
  6. "max": 10
  7. },
  8. "query": {
  9. "match": {
  10. "message": "foo"
  11. }
  12. }
  13. }

追加のみの時間ベースのインデックスの場合、timestampフィールドは安全に使用できます。