検索結果のページネーション
デフォルトでは、検索は最初の10件の一致するヒットを返します。より大きな結果セットをページングするには、search APIのfrom
およびsize
パラメータを使用できます。from
パラメータはスキップするヒットの数を定義し、デフォルトは0
です。size
パラメータは返す最大ヒット数です。これら2つのパラメータを組み合わせることで、結果のページを定義します。
Python
resp = client.search(
from_=5,
size=20,
query={
"match": {
"user.id": "kimchy"
}
},
)
print(resp)
Ruby
response = client.search(
body: {
from: 5,
size: 20,
query: {
match: {
'user.id' => 'kimchy'
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"from": 5,
"size": 20,
"query": {
"match": {
"user.id": "kimchy"
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
from: 5,
size: 20,
query: {
match: {
"user.id": "kimchy",
},
},
});
console.log(response);
コンソール
GET /_search
{
"from": 5,
"size": 20,
"query": {
"match": {
"user.id": "kimchy"
}
}
}
デフォルトでは、`````from`````および`````size`````を使用して10,000件以上のヒットをページングすることはできません。この制限は、[`````index.max_result_window`````](0dd33dc5c1f7a36a.md#index-max-result-window)インデックス設定によって設定された保護策です。10,000件以上のヒットをページングする必要がある場合は、代わりに[`````search_after`````](238327401f76c2ac.md#search-after)パラメータを使用してください。
Elasticsearchは、Luceneの内部ドキュメントIDをタイブレーカーとして使用します。これらの内部ドキュメントIDは、同じデータのレプリカ間で完全に異なる場合があります。検索ヒットをページングする際、同じソート値を持つドキュメントが一貫して順序付けられないことがあります。
## 検索後
`````search_after`````パラメータを使用して、前のページからの一連の[ソート値](/read/elasticsearch-8-15/edefedafdd88134e.md)を使用して次のページのヒットを取得できます。
`````search_after`````を使用するには、同じ`````query`````および`````sort`````値を持つ複数の検索リクエストが必要です。最初のステップは、初期リクエストを実行することです。次の例では、結果を2つのフィールド(`````date`````および`````tie_breaker_id`````)でソートします:
#### Python
``````python
resp = client.search(
index="twitter",
query={
"match": {
"title": "elasticsearch"
}
},
sort=[
{
"date": "asc"
},
{
"tie_breaker_id": "asc"
}
],
)
print(resp)
`
Ruby
response = client.search(
index: 'twitter',
body: {
query: {
match: {
title: 'elasticsearch'
}
},
sort: [
{
date: 'asc'
},
{
tie_breaker_id: 'asc'
}
]
}
)
puts response
Js
const response = await client.search({
index: "twitter",
query: {
match: {
title: "elasticsearch",
},
},
sort: [
{
date: "asc",
},
{
tie_breaker_id: "asc",
},
],
});
console.log(response);
コンソール
GET twitter/_search
{
"query": {
"match": {
"title": "elasticsearch"
}
},
"sort": [
{"date": "asc"},
{"tie_breaker_id": "asc"}
]
}
_id フィールドのコピー、doc_values が有効になっています |
コンソール-結果
{
"took" : 17,
"timed_out" : false,
"_shards" : ...,
"hits" : {
"total" : ...,
"max_score" : null,
"hits" : [
...
{
"_index" : "twitter",
"_id" : "654322",
"_score" : null,
"_source" : ...,
"sort" : [
1463538855,
"654322"
]
},
{
"_index" : "twitter",
"_id" : "654323",
"_score" : null,
"_source" : ...,
"sort" : [
1463538857,
"654323"
]
}
]
}
}
最後に返されたヒットのソート値。 |
次のページの結果を取得するには、リクエストを繰り返し、最後のヒットからsort
値を取得し、それをsearch_after
配列に挿入します:
Python
resp = client.search(
index="twitter",
query={
"match": {
"title": "elasticsearch"
}
},
search_after=[
1463538857,
"654323"
],
sort=[
{
"date": "asc"
},
{
"tie_breaker_id": "asc"
}
],
)
print(resp)
Ruby
response = client.search(
index: 'twitter',
body: {
query: {
match: {
title: 'elasticsearch'
}
},
search_after: [
1_463_538_857,
'654323'
],
sort: [
{
date: 'asc'
},
{
tie_breaker_id: 'asc'
}
]
}
)
puts response
Js
const response = await client.search({
index: "twitter",
query: {
match: {
title: "elasticsearch",
},
},
search_after: [1463538857, "654323"],
sort: [
{
date: "asc",
},
{
tie_breaker_id: "asc",
},
],
});
console.log(response);
コンソール
GET twitter/_search
{
"query": {
"match": {
"title": "elasticsearch"
}
},
"search_after": [1463538857, "654323"],
"sort": [
{"date": "asc"},
{"tie_breaker_id": "asc"}
]
}
このプロセスを繰り返し、毎回新しいページの結果を取得するたびにsearch_after
配列を更新します。これらのリクエストの間にリフレッシュが発生すると、結果の順序が変わる可能性があり、ページ間で一貫性のない結果を引き起こす可能性があります。これを防ぐために、検索中に現在のインデックス状態を保持するためにポイントインタイム(PIT)を作成できます。
Python
resp = client.open_point_in_time(
index="my-index-000001",
keep_alive="1m",
)
print(resp)
Ruby
response = client.open_point_in_time(
index: 'my-index-000001',
keep_alive: '1m'
)
puts response
Js
const response = await client.openPointInTime({
index: "my-index-000001",
keep_alive: "1m",
});
console.log(response);
コンソール
POST /my-index-000001/_pit?keep_alive=1m
コンソール-結果
{
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}
最初のページの結果を取得するには、sort
引数を持つ検索リクエストを送信します。PITを使用する場合は、pit.id
パラメータにPIT IDを指定し、リクエストパスからターゲットデータストリームまたはインデックスを省略します。
すべてのPIT検索リクエストは、_shard_doc
と呼ばれる暗黙のソートタイブレーカーフィールドを追加します。これは明示的に提供することもできます。PITを使用できない場合は、sort
にタイブレーカーフィールドを含めることをお勧めします。このタイブレーカーフィールドは、各ドキュメントに対して一意の値を含む必要があります。タイブレーカーフィールドを含めない場合、ページングされた結果がヒットを見逃したり重複したりする可能性があります。
検索後リクエストには、ソート順が_shard_doc
で、総ヒット数が追跡されていない場合に、より高速にするための最適化があります。順序に関係なくすべてのドキュメントを反復処理したい場合、これが最も効率的なオプションです。
#### Python
``````python
resp = client.search(
size=10000,
query={
"match": {
"user.id": "elkbee"
}
},
pit={
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
sort=[
{
"@timestamp": {
"order": "asc",
"format": "strict_date_optional_time_nanos",
"numeric_type": "date_nanos"
}
}
],
)
print(resp)
`
Js
const response = await client.search({
size: 10000,
query: {
match: {
"user.id": "elkbee",
},
},
pit: {
id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
keep_alive: "1m",
},
sort: [
{
"@timestamp": {
order: "asc",
format: "strict_date_optional_time_nanos",
numeric_type: "date_nanos",
},
},
],
});
console.log(response);
コンソール
GET /_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"pit": {
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
"sort": [
{"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos", "numeric_type" : "date_nanos" }}
]
}
検索のためのPIT ID。 | |
_shard_doc 昇順での検索のためのヒットをソートします。 |
検索応答には、各ヒットのsort
値の配列が含まれます。PITを使用した場合、タイブレーカーは各ヒットの最後のsort
値として含まれます。このタイブレーカーは、PITを使用するすべての検索リクエストで自動的に追加されます。_shard_doc
値は、PIT内のシャードインデックスとLuceneの内部ドキュメントIDの組み合わせであり、各ドキュメントごとに一意で、PIT内で一定です。検索リクエストでタイブレーカーを明示的に追加して順序をカスタマイズすることもできます:
Python
resp = client.search(
size=10000,
query={
"match": {
"user.id": "elkbee"
}
},
pit={
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
sort=[
{
"@timestamp": {
"order": "asc",
"format": "strict_date_optional_time_nanos"
}
},
{
"_shard_doc": "desc"
}
],
)
print(resp)
Js
const response = await client.search({
size: 10000,
query: {
match: {
"user.id": "elkbee",
},
},
pit: {
id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
keep_alive: "1m",
},
sort: [
{
"@timestamp": {
order: "asc",
format: "strict_date_optional_time_nanos",
},
},
{
_shard_doc: "desc",
},
],
});
console.log(response);
コンソール
GET /_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"pit": {
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
"sort": [
{"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos"}},
{"_shard_doc": "desc"}
]
}
検索のためのPIT ID。 | |
_shard_doc 降順での検索のためのヒットをソートします。 |
コンソール-結果
{
"pit_id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"took" : 17,
"timed_out" : false,
"_shards" : ...,
"hits" : {
"total" : ...,
"max_score" : null,
"hits" : [
...
{
"_index" : "my-index-000001",
"_id" : "FaslK3QBySSL_rrj9zM5",
"_score" : null,
"_source" : ...,
"sort" : [
"2021-05-20T05:30:04.832Z",
4294967298
]
}
]
}
}
時間のポイントのために更新されたid 。 |
|
最後に返されたヒットのソート値。 | |
pit_id 内の各ドキュメントごとに一意のタイブレーカー値。 |
次のページの結果を取得するには、前の検索を再実行し、最後のヒットのソート値(タイブレーカーを含む)をsearch_after
引数として使用します。PITを使用する場合は、pit.id
パラメータに最新のPIT IDを使用します。検索のquery
およびsort
引数は変更されるべきではありません。提供されている場合、from
引数は0
(デフォルト)または-1
でなければなりません。
Python
resp = client.search(
size=10000,
query={
"match": {
"user.id": "elkbee"
}
},
pit={
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
sort=[
{
"@timestamp": {
"order": "asc",
"format": "strict_date_optional_time_nanos"
}
}
],
search_after=[
"2021-05-20T05:30:04.832Z",
4294967298
],
track_total_hits=False,
)
print(resp)
Js
const response = await client.search({
size: 10000,
query: {
match: {
"user.id": "elkbee",
},
},
pit: {
id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
keep_alive: "1m",
},
sort: [
{
"@timestamp": {
order: "asc",
format: "strict_date_optional_time_nanos",
},
},
],
search_after: ["2021-05-20T05:30:04.832Z", 4294967298],
track_total_hits: false,
});
console.log(response);
コンソール
GET /_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"pit": {
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
"sort": [
{"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos"}}
],
"search_after": [
"2021-05-20T05:30:04.832Z",
4294967298
],
"track_total_hits": false
}
前の検索によって返されたPIT ID。 | |
前の検索の最後のヒットからのソート値。 | |
ページングを高速化するために総ヒット数の追跡を無効にします。 |
このプロセスを繰り返して、追加のページの結果を取得できます。PITを使用する場合は、各検索リクエストのkeep_alive
パラメータを使用してPITの保持期間を延長できます。
終了したら、PITを削除する必要があります。
Python
resp = client.close_point_in_time(
id="46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
)
print(resp)
Ruby
response = client.close_point_in_time(
body: {
id: '46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=='
}
)
puts response
Js
const response = await client.closePointInTime({
id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
});
console.log(response);
コンソール
DELETE /_pit
{
"id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}
スクロール検索結果
深いページネーションのためにスクロールAPIの使用はもはや推奨されていません。10,000件以上のヒットをページングする際にインデックス状態を保持する必要がある場合は、search_after
パラメータをポイントインタイム(PIT)とともに使用してください。
search
リクエストは単一の「ページ」の結果を返しますが、scroll
APIは、従来のデータベースでカーソルを使用するのと同じように、単一の検索リクエストから大量の結果(またはすべての結果)を取得するために使用できます。
スクロールはリアルタイムのユーザーリクエストを意図したものではなく、大量のデータを処理するためのものです。たとえば、1つのデータストリームまたはインデックスの内容を新しいデータストリームまたはインデックスに再インデックスするために使用されます。
スクロールと再インデックスのクライアントサポート
公式にサポートされているクライアントのいくつかは、スクロール検索と再インデックスを支援するヘルパーを提供します:
- Perl
- Search::Elasticsearch::Client::5_0::BulkおよびSearch::Elasticsearch::Client::5_0::Scrollを参照してください
- Python
- elasticsearch.helpers.*を参照してください
- JavaScript
- client.helpers.*を参照してください
スクロールリクエストから返される結果は、初期search
リクエストが行われた時点でのデータストリームまたはインデックスの状態を反映しています。ドキュメントへのその後の変更(インデックス、更新、または削除)は、後の検索リクエストにのみ影響します。
スクロールを使用するには、初期検索リクエストでscroll
パラメータをクエリ文字列に指定する必要があります。これにより、Elasticsearchは「検索コンテキスト」をどれくらいの間保持する必要があるかを指示します(検索コンテキストを保持するを参照)。例:?scroll=1m
。
Python
resp = client.search(
index="my-index-000001",
scroll="1m",
size=100,
query={
"match": {
"message": "foo"
}
},
)
print(resp)
Ruby
response = client.search(
index: 'my-index-000001',
scroll: '1m',
body: {
size: 100,
query: {
match: {
message: 'foo'
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
scroll: "1m",
size: 100,
query: {
match: {
message: "foo",
},
},
});
console.log(response);
コンソール
POST /my-index-000001/_search?scroll=1m
{
"size": 100,
"query": {
"match": {
"message": "foo"
}
}
}
上記のリクエストからの結果には、次のバッチの結果を取得するために_scroll_id
をscroll
APIに渡す必要があります。
Python
resp = client.scroll(
scroll="1m",
scroll_id="DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
)
print(resp)
Go
res, err := es.Scroll(
es.Scroll.WithBody(strings.NewReader(`{
"scroll": "1m",
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}`)),
es.Scroll.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.scroll({
scroll: "1m",
scroll_id: "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
});
console.log(response);
コンソール
POST /_search/scroll
{
"scroll" : "1m",
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
GET またはPOST を使用でき、URLにはindex 名前—これは元の search リクエストで指定されます。 |
|
scroll パラメータは、Elasticsearchに検索コンテキストを1m の間オープンに保つよう指示します。 |
|
scroll_id パラメータ |
size
パラメータを使用すると、各バッチの結果で返される最大ヒット数を構成できます。scroll
APIへの各呼び出しは、結果が返されるまで次のバッチを返します。つまり、hits
配列が空になります。
初期検索リクエストと各後続のスクロールリクエストはそれぞれ_scroll_id
を返します。_scroll_id
はリクエスト間で変更される可能性がありますが、常に変更されるわけではありません。いずれにせよ、最近受信した_scroll_id
のみを使用する必要があります。
リクエストが集約を指定している場合、初期検索応答のみが集約結果を含みます。
スクロールリクエストには、ソート順が_doc
の場合にそれを高速化する最適化があります。順序に関係なくすべてのドキュメントを反復処理したい場合、これが最も効率的なオプションです:
Php
$params = [
'body' => [
'sort' => [
'_doc',
],
],
];
$response = $client->search($params);
Python
resp = client.search(
scroll="1m",
sort=[
"_doc"
],
)
print(resp)
Ruby
response = client.search(
scroll: '1m',
body: {
sort: [
'_doc'
]
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"sort": [
"_doc"
]
}`)),
es.Search.WithScroll(time.Duration(60000000000)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
scroll: "1m",
sort: ["_doc"],
});
console.log(response);
コンソール
GET /_search?scroll=1m
{
"sort": [
"_doc"
]
}
検索コンテキストを保持する
スクロールは、初期検索リクエストの時点で一致したすべてのドキュメントを返します。これらのドキュメントへのその後の変更は無視されます。scroll_id
は、Elasticsearchが正しいドキュメントを返すために必要なすべてを追跡する検索コンテキストを識別します。検索コンテキストは初期リクエストによって作成され、その後のリクエストによって保持されます。
通常、バックグラウンドマージプロセスは、インデックスを最適化するために小さなセグメントをマージして新しい大きなセグメントを作成します。小さなセグメントがもはや必要ない場合、それらは削除されます。このプロセスはスクロール中も続きますが、オープンな検索コンテキストは古いセグメントが削除されるのを防ぎます。なぜなら、それらはまだ使用中だからです。
古いセグメントを保持することは、より多くのディスクスペースとファイルハンドルが必要であることを意味します。ノードが十分な空きファイルハンドルを持つように構成されていることを確認してください。[ファイル記述子](/read/elasticsearch-8-15/cf63fb43f29c7f55.md)を参照してください。
さらに、セグメントに削除または更新されたドキュメントが含まれている場合、検索コンテキストは、初期検索リクエストの時点でセグメント内の各ドキュメントがライブであったかどうかを追跡する必要があります。進行中の削除または更新があるインデックスで多くのオープンスクロールがある場合、ノードに十分なヒープスペースがあることを確認してください。
オープンスクロールが多すぎることによって引き起こされる問題を防ぐために、ユーザーは特定の制限を超えてスクロールを開くことは許可されていません。デフォルトでは、オープンスクロールの最大数は500です。この制限は、`````search.max_open_scroll_context`````クラスター設定で更新できます。
オープンな検索コンテキストがいくつあるかを確認するには、[ノード統計API](/read/elasticsearch-8-15/b347631483fdb07e.md)を使用してください。
#### Php
``````php
$params = [
'metric' => 'indices',
'index_metric' => 'search',
];
$response = $client->nodes()->stats($params);
`
Python
resp = client.nodes.stats(
metric="indices",
index_metric="search",
)
print(resp)
Ruby
response = client.nodes.stats(
metric: 'indices',
index_metric: 'search'
)
puts response
Go
res, err := es.Nodes.Stats(
es.Nodes.Stats.WithMetric([]string{"indices"}...),
es.Nodes.Stats.WithIndexMetric([]string{"search"}...),
)
fmt.Println(res, err)
Js
const response = await client.nodes.stats({
metric: "indices",
index_metric: "search",
});
console.log(response);
コンソール
GET /_nodes/stats/indices/search
スクロールのクリア
検索コンテキストは、scroll
タイムアウトが超えたときに自動的に削除されます。ただし、スクロールをオープンに保つことにはコストがかかります。前のセクションで説明したように、スクロールはもはや使用されていない場合は、clear-scroll
APIを使用して明示的にクリアする必要があります。
Python
resp = client.clear_scroll(
scroll_id="DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
)
print(resp)
Ruby
response = client.clear_scroll(
body: {
scroll_id: 'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=='
}
)
puts response
Go
res, err := es.ClearScroll(
es.ClearScroll.WithBody(strings.NewReader(`{
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}`)),
)
fmt.Println(res, err)
Js
const response = await client.clearScroll({
scroll_id: "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
});
console.log(response);
コンソール
DELETE /_search/scroll
{
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
複数のスクロールIDを配列として渡すことができます:
Python
resp = client.clear_scroll(
scroll_id=[
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
],
)
print(resp)
Ruby
response = client.clear_scroll(
body: {
scroll_id: [
'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==',
'DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB'
]
}
)
puts response
Go
res, err := es.ClearScroll(
es.ClearScroll.WithBody(strings.NewReader(`{
"scroll_id": [
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
]
}`)),
)
fmt.Println(res, err)
Js
const response = await client.clearScroll({
scroll_id: [
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB",
],
});
console.log(response);
コンソール
DELETE /_search/scroll
{
"scroll_id" : [
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
]
}
すべての検索コンテキストは、_all
パラメータでクリアできます:
Php
$params = [
'scroll_id' => '_all',
];
$response = $client->clearScroll($params);
Python
resp = client.clear_scroll(
scroll_id="_all",
)
print(resp)
Ruby
response = client.clear_scroll(
scroll_id: '_all'
)
puts response
Go
res, err := es.ClearScroll(
es.ClearScroll.WithScrollID("_all"),
)
fmt.Println(res, err)
Js
const response = await client.clearScroll({
scroll_id: "_all",
});
console.log(response);
コンソール
DELETE /_search/scroll/_all
#### Php
``````php
$params = [
'scroll_id' => 'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB',
];
$response = $client->clearScroll($params);
`
Python
resp = client.clear_scroll(
scroll_id="DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB",
)
print(resp)
Ruby
response = client.clear_scroll(
scroll_id: 'DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB'
)
puts response
Go
res, err := es.ClearScroll(
es.ClearScroll.WithScrollID("DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==", "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"),
)
fmt.Println(res, err)
Js
const response = await client.clearScroll({
scroll_id:
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB",
});
console.log(response);
コンソール
DELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB
スライスされたスクロール
大量のドキュメントをページングする際、検索を複数のスライスに分割して独立して消費することが役立ちます:
Python
resp = client.search(
index="my-index-000001",
scroll="1m",
slice={
"id": 0,
"max": 2
},
query={
"match": {
"message": "foo"
}
},
)
print(resp)
resp1 = client.search(
index="my-index-000001",
scroll="1m",
slice={
"id": 1,
"max": 2
},
query={
"match": {
"message": "foo"
}
},
)
print(resp1)
Ruby
response = client.search(
index: 'my-index-000001',
scroll: '1m',
body: {
slice: {
id: 0,
max: 2
},
query: {
match: {
message: 'foo'
}
}
}
)
puts response
response = client.search(
index: 'my-index-000001',
scroll: '1m',
body: {
slice: {
id: 1,
max: 2
},
query: {
match: {
message: 'foo'
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
scroll: "1m",
slice: {
id: 0,
max: 2,
},
query: {
match: {
message: "foo",
},
},
});
console.log(response);
const response1 = await client.search({
index: "my-index-000001",
scroll: "1m",
slice: {
id: 1,
max: 2,
},
query: {
match: {
message: "foo",
},
},
});
console.log(response1);
コンソール
GET /my-index-000001/_search?scroll=1m
{
"slice": {
"id": 0,
"max": 2
},
"query": {
"match": {
"message": "foo"
}
}
}
GET /my-index-000001/_search?scroll=1m
{
"slice": {
"id": 1,
"max": 2
},
"query": {
"match": {
"message": "foo"
}
}
}
スライスの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
resp = client.search(
index="my-index-000001",
scroll="1m",
slice={
"field": "@timestamp",
"id": 0,
"max": 10
},
query={
"match": {
"message": "foo"
}
},
)
print(resp)
Ruby
response = client.search(
index: 'my-index-000001',
scroll: '1m',
body: {
slice: {
field: '@timestamp',
id: 0,
max: 10
},
query: {
match: {
message: 'foo'
}
}
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
scroll: "1m",
slice: {
field: "@timestamp",
id: 0,
max: 10,
},
query: {
match: {
message: "foo",
},
},
});
console.log(response);
コンソール
GET /my-index-000001/_search?scroll=1m
{
"slice": {
"field": "@timestamp",
"id": 0,
"max": 10
},
"query": {
"match": {
"message": "foo"
}
}
}
追加のみの時間ベースのインデックスの場合、timestamp
フィールドは安全に使用できます。