Terms aggregation
ユニークな値ごとにバケットが動的に構築されるマルチバケット値ソースベースの集約です。
例:
Python
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre"
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre'
}
}
}
}
)
puts response
Js
const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"genres": {
"terms": { "field": "genre" }
}
}
}
Console-Result
{
...
"aggregations": {
"genres": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "electronic",
"doc_count": 6
},
{
"key": "rock",
"doc_count": 3
},
{
"key": "jazz",
"doc_count": 2
}
]
}
}
}
各用語のドキュメント数に対する誤差の上限、以下を参照してください。 | |
ユニークな用語が多数ある場合、Elasticsearchは上位の用語のみを返します。この数は、レスポンスに含まれないすべてのバケットのドキュメント数の合計です。 | |
上位バケットのリスト、top の意味は順序によって定義されます。 |
field
はKeyword、Numeric、ip
、boolean
、またはbinary
です。
デフォルトでは、terms
集約をtext
フィールドで実行することはできません。代わりに、keyword
サブフィールドを使用してください。あるいは、text
フィールドでfielddata
を有効にして、フィールドの分析された用語のバケットを作成できます。fielddata
を有効にすると、メモリ使用量が大幅に増加する可能性があります。
Size
デフォルトでは、terms
集約は最も多くのドキュメントを持つ上位10用語を返します。size
パラメータを使用して、search.max_buckets制限まで、より多くの用語を返すことができます。
データに100または1000のユニークな用語が含まれている場合、size
のterms
集約を増やしてすべてを返すことができます。ユニークな用語がさらに多く、すべてが必要な場合は、コンポジット集約を使用してください。
## Shard size
より正確な結果を得るために、`````terms````` aggは各シャードから上位`````size`````用語以上のものを取得します。上位`````shard_size`````用語を取得し、デフォルトは`````size * 1.5 + 10`````です。
これは、1つの用語が1つのシャードに多くのドキュメントを持っているが、他のすべてのシャードで`````size`````しきい値の直下にある場合に対処するためです。各シャードが`````size`````用語のみを返す場合、集約はその用語の部分的なドキュメント数を返します。したがって、`````terms`````は欠落している用語をキャッチしようとするために、より多くの用語を返します。これは役立ちますが、用語の部分的なドキュメント数を返す可能性がまだあります。これは、シャードごとのドキュメント数が異なる用語を必要とします。
`````shard_size`````を増やすことで、これらの異なるドキュメント数をよりよく考慮し、上位用語の選択の精度を向上させることができます。`````shard_size`````を増やす方が、`````size`````を増やすよりもはるかに安価です。ただし、ワイヤー上でより多くのバイトを必要とし、コーディネートノードのメモリで待機します。
このガイダンスは、`````terms`````集約のデフォルトのソート`````order`````を使用している場合にのみ適用されます。ドキュメント数の降順以外でソートしている場合は、[順序](aa21c04ecd5a1ccc.md#search-aggregations-bucket-terms-aggregation-order)を参照してください。
`````shard_size`````は`````size`````より小さくすることはできません(あまり意味がありません)。そうなった場合、Elasticsearchはそれを上書きし、`````size`````と等しくリセットします。
## Document count error
より大きな`````shard_size`````値でも、`````doc_count`````値は`````terms`````集約に対して近似的である可能性があります。その結果、`````terms`````集約に対するサブ集約も近似的である可能性があります。
`````sum_other_doc_count`````は、上位`````size`````用語に入らなかったドキュメントの数です。これが`````0`````より大きい場合、`````terms````` aggは、コーディネートノードの`````size`````に収まらなかったか、データノードの`````shard_size`````に収まらなかったため、いくつかのバケットを捨てなければならなかったことが確実です。
## Per bucket document count error
`````show_term_doc_count_error`````パラメータを`````true`````に設定すると、`````terms`````集約には`````doc_count_error_upper_bound`````が含まれ、これは各シャードによって返される`````doc_count`````の誤差の上限です。これは、`````shard_size`````に収まらなかった各シャードの最大バケットのサイズの合計です。
具体的には、1つのシャードで非常に大きなバケットがあり、他のすべてのシャードで`````shard_size`````の直下にある場合を想像してください。その場合、`````terms````` aggはそのバケットを返しますが、`````shard_size`````しきい値を下回ったシャードの多くのドキュメントからのデータが欠落しています。`````doc_count_error_upper_bound`````は、その欠落したドキュメントの最大数です。
#### Python
``````python
resp = client.search(
aggs={
"products": {
"terms": {
"field": "product",
"size": 5,
"show_term_doc_count_error": True
}
}
},
)
print(resp)
`
Ruby
response = client.search(
body: {
aggregations: {
products: {
terms: {
field: 'product',
size: 5,
show_term_doc_count_error: true
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"products": {
"terms": {
"field": "product",
"size": 5,
"show_term_doc_count_error": true
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
products: {
terms: {
field: "product",
size: 5,
show_term_doc_count_error: true,
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"products": {
"terms": {
"field": "product",
"size": 5,
"show_term_doc_count_error": true
}
}
}
}
これらのエラーは、用語がドキュメント数の降順で並べられている場合にのみこの方法で計算できます。集約が用語の値自体(昇順または降順)で並べられている場合、他のシャードからの結果に表示される特定の用語を返さないシャードがある場合、そのシャードにはその用語がインデックスに存在しないため、ドキュメント数にエラーはありません。集約がサブ集約でソートされているか、ドキュメント数の昇順でソートされている場合、ドキュメント数のエラーは決定できず、-1の値が与えられます。
Order
デフォルトでは、terms
集約は用語をドキュメント_count
の降順で並べます。これにより、Elasticsearchが報告できる制限されたドキュメント数の誤差が生成されます。
特に`````"order": { "_count": "asc" }`````の使用を避けてください。珍しい用語を見つける必要がある場合は、代わりに[`````rare_terms`````](/read/elasticsearch-8-15/4ba2f49082b46b81.md)集約を使用してください。`````terms`````集約が[シャードから用語を取得する方法](aa21c04ecd5a1ccc.md#search-aggregations-bucket-terms-aggregation-shard-size)のため、ドキュメント数の昇順でソートすると、しばしば不正確な結果が得られます。
### Ordering by the term value
この場合、バケットは実際の用語の値によって順序付けられます。たとえば、キーワードの辞書順や数値の数値順です。このソートは、昇順および降順の両方で安全であり、正確な結果を生成します。
用語を昇順でアルファベット順に並べるバケットの例:
#### Python
``````python
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"_key": "asc"
}
}
}
},
)
print(resp)
`
Ruby
response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
_key: 'asc'
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": {
"_key": "asc"
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
order: {
_key: "asc",
},
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "_key": "asc" }
}
}
}
}
Ordering by a sub aggregation
サブ集約によるソートは、terms
集約がシャードから結果を取得する方法のため、一般的に不正確な順序を生成します。
サブ集約の順序が安全で正しい結果を返す場合は2つのケースがあります:降順での最大値によるソート、または昇順での最小値によるソートです。これらのアプローチは、サブ集約の動作と一致するために機能します。つまり、最大の最大値または最小の最小値を探している場合、グローバルな回答(結合されたシャードから)は、ローカルシャードの回答の1つに含まれている必要があります。逆に、最小の最大値と最大の最小値は正確に計算されません。
これらのケースでは、順序は正しいですが、ドキュメント数と非順序のサブ集約にはまだエラーがある可能性があります(Elasticsearchはそれらのエラーの境界を計算しません)。
単一値メトリックサブ集約によるバケットの順序(集約名で識別):
Python
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"max_play_count": "desc"
}
},
"aggs": {
"max_play_count": {
"max": {
"field": "play_count"
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
max_play_count: 'desc'
}
},
aggregations: {
max_play_count: {
max: {
field: 'play_count'
}
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": {
"max_play_count": "desc"
}
},
"aggs": {
"max_play_count": {
"max": {
"field": "play_count"
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
order: {
max_play_count: "desc",
},
},
aggs: {
max_play_count: {
max: {
field: "play_count",
},
},
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "max_play_count": "desc" }
},
"aggs": {
"max_play_count": { "max": { "field": "play_count" } }
}
}
}
}
マルチバリューメトリックサブ集約によるバケットの順序(集約名で識別):
Python
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"playback_stats.max": "desc"
}
},
"aggs": {
"playback_stats": {
"stats": {
"field": "play_count"
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
'playback_stats.max' => 'desc'
}
},
aggregations: {
playback_stats: {
stats: {
field: 'play_count'
}
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": {
"playback_stats.max": "desc"
}
},
"aggs": {
"playback_stats": {
"stats": {
"field": "play_count"
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
order: {
"playback_stats.max": "desc",
},
},
aggs: {
playback_stats: {
stats: {
field: "play_count",
},
},
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "playback_stats.max": "desc" }
},
"aggs": {
"playback_stats": { "stats": { "field": "play_count" } }
}
}
}
}
Pipeline aggs cannot be used for sorting
パイプライン集約は、すべての他の集約が完了した後のリデュースフェーズ中に実行されます。このため、順序付けに使用することはできません。
バケットの順序を階層内の「より深い」集約に基づいて行うことも可能です。これは、集約パスが単一バケットタイプであり、パス内の最後の集約が単一バケットまたはメトリックのいずれかである限りサポートされます。単一バケットタイプの場合、順序はバケット内のドキュメント数(すなわちdoc_count
)によって定義され、メトリックの場合は、上記のルールが適用されます(マルチバリューメトリック集約の場合は、ソートするメトリック名を示す必要があり、単一バリューメトリック集約の場合は、その値に対してソートが適用されます)。
パスは次の形式で定義する必要があります:
Ebnf
AGG_SEPARATOR = '>' ;
METRIC_SEPARATOR = '.' ;
AGG_NAME = <the name of the aggregation> ;
METRIC = <the name of the metric (in case of multi-value metrics aggregation)> ;
PATH = <AGG_NAME> [ <AGG_SEPARATOR>, <AGG_NAME> ]* [ <METRIC_SEPARATOR>, <METRIC> ] ;
Python
resp = client.search(
aggs={
"countries": {
"terms": {
"field": "artist.country",
"order": {
"rock>playback_stats.avg": "desc"
}
},
"aggs": {
"rock": {
"filter": {
"term": {
"genre": "rock"
}
},
"aggs": {
"playback_stats": {
"stats": {
"field": "play_count"
}
}
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
countries: {
terms: {
field: 'artist.country',
order: {
"rock>playback_stats.avg": 'desc'
}
},
aggregations: {
rock: {
filter: {
term: {
genre: 'rock'
}
},
aggregations: {
playback_stats: {
stats: {
field: 'play_count'
}
}
}
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"countries": {
"terms": {
"field": "artist.country",
"order": {
"rock>playback_stats.avg": "desc"
}
},
"aggs": {
"rock": {
"filter": {
"term": {
"genre": "rock"
}
},
"aggs": {
"playback_stats": {
"stats": {
"field": "play_count"
}
}
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
countries: {
terms: {
field: "artist.country",
order: {
"rock>playback_stats.avg": "desc",
},
},
aggs: {
rock: {
filter: {
term: {
genre: "rock",
},
},
aggs: {
playback_stats: {
stats: {
field: "play_count",
},
},
},
},
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"countries": {
"terms": {
"field": "artist.country",
"order": { "rock>playback_stats.avg": "desc" }
},
"aggs": {
"rock": {
"filter": { "term": { "genre": "rock" } },
"aggs": {
"playback_stats": { "stats": { "field": "play_count" } }
}
}
}
}
}
}
上記は、アーティストの国のバケットをロックソングの平均再生回数に基づいてソートします。
次のように、バケットを順序付けるために複数の基準を使用できます:
Python
resp = client.search(
aggs={
"countries": {
"terms": {
"field": "artist.country",
"order": [
{
"rock>playback_stats.avg": "desc"
},
{
"_count": "desc"
}
]
},
"aggs": {
"rock": {
"filter": {
"term": {
"genre": "rock"
}
},
"aggs": {
"playback_stats": {
"stats": {
"field": "play_count"
}
}
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
countries: {
terms: {
field: 'artist.country',
order: [
{
"rock>playback_stats.avg": 'desc'
},
{
_count: 'desc'
}
]
},
aggregations: {
rock: {
filter: {
term: {
genre: 'rock'
}
},
aggregations: {
playback_stats: {
stats: {
field: 'play_count'
}
}
}
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"countries": {
"terms": {
"field": "artist.country",
"order": [
{
"rock>playback_stats.avg": "desc"
},
{
"_count": "desc"
}
]
},
"aggs": {
"rock": {
"filter": {
"term": {
"genre": "rock"
}
},
"aggs": {
"playback_stats": {
"stats": {
"field": "play_count"
}
}
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
countries: {
terms: {
field: "artist.country",
order: [
{
"rock>playback_stats.avg": "desc",
},
{
_count: "desc",
},
],
},
aggs: {
rock: {
filter: {
term: {
genre: "rock",
},
},
aggs: {
playback_stats: {
stats: {
field: "play_count",
},
},
},
},
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"countries": {
"terms": {
"field": "artist.country",
"order": [ { "rock>playback_stats.avg": "desc" }, { "_count": "desc" } ]
},
"aggs": {
"rock": {
"filter": { "term": { "genre": "rock" } },
"aggs": {
"playback_stats": { "stats": { "field": "play_count" } }
}
}
}
}
}
}
上記は、アーティストの国のバケットをロックソングの平均再生回数に基づいてソートし、その後doc_count
を降順でソートします。
2つのバケットがすべての順序基準で同じ値を共有する場合、バケットの用語値が昇順のアルファベット順でタイブレーカーとして使用され、バケットの非決定的な順序を防ぎます。
Ordering by count ascending
ドキュメント_count
の昇順で用語を並べると、Elasticsearchが正確に報告できない無制限の誤差が生成されます。したがって、以下の例に示すように、"order": { "_count": "asc" }
の使用は強く推奨しません。
Python
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"_count": "asc"
}
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
_count: 'asc'
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": {
"_count": "asc"
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
order: {
_count: "asc",
},
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "_count": "asc" }
}
}
}
}
Minimum document count
設定されたヒット数を超える用語のみを返すことが可能です。これは、min_doc_count
オプションを使用して行います。
Python
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"min_doc_count": 10
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
min_doc_count: 10
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"min_doc_count": 10
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
min_doc_count: 10,
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"min_doc_count": 10
}
}
}
}
上記の集約は、10ヒット以上で見つかったタグのみを返します。デフォルト値は1
です。
用語はシャードレベルで収集され、他のシャードから収集された用語と2番目のステップでマージされます。ただし、シャードはグローバルなドキュメント数に関する情報を持っていません。用語が候補リストに追加されるかどうかの決定は、ローカルシャード頻度を使用してシャード上で計算された順序のみに依存します。min_doc_count
基準は、すべてのシャードのローカル用語統計をマージした後にのみ適用されます。ある意味で、候補として用語を追加する決定は、その用語が実際に必要なmin_doc_count
に達するかどうかについてあまり「確実」ではない状態で行われます。これにより、候補リストに低頻度の用語が含まれている場合、最終結果に多くの(グローバルに)高頻度の用語が欠落する可能性があります。これを避けるために、shard_size
パラメータを増やして、シャード上でより多くの候補用語を許可できます。ただし、これによりメモリ消費量とネットワークトラフィックが増加します。
shard_min_doc_count
パラメータshard_min_doc_count
は、用語が候補リストに追加されるべきかどうかに関するシャードの「確実性」を調整します。用語は、セット内のローカルシャード頻度がmin_doc_count
より高い場合にのみ考慮されます。辞書に多くの低頻度の用語が含まれており、それに興味がない場合(たとえば、誤字など)、shard_min_doc_count
パラメータを設定して、ローカルカウントをマージした後に必要なmin_doc_count
に達しない可能性が高い候補用語をシャードレベルでフィルタリングできます。shard_min_doc_count
はデフォルトで0
に設定されており、明示的に設定しない限り効果はありません。
`````doc_count`````降順でソートしていない場合、高い`````min_doc_count`````値は、シャードから収集されたデータが不十分なため、`````size`````より少ないバケットを返す可能性があります。欠落したバケットは、`````shard_size`````を増やすことで戻すことができます。`````shard_min_doc_count`````を高く設定しすぎると、シャードレベルで用語がフィルタリングされます。この値は、`````min_doc_count/#shards`````よりもはるかに低く設定する必要があります。
## Script
ドキュメント内のデータが集約したい内容と正確に一致しない場合は、[ランタイムフィールド](/read/elasticsearch-8-15/41c27b1e9f3afe47.md)を使用してください。たとえば、「アンソロジー」を特別なカテゴリにする必要がある場合、次のように実行できます:
#### Python
``````python
resp = client.search(
size=0,
runtime_mappings={
"normalized_genre": {
"type": "keyword",
"script": "\n String genre = doc['genre'].value;\n if (doc['product'].value.startsWith('Anthology')) {\n emit(genre + ' anthology');\n } else {\n emit(genre);\n }\n "
}
},
aggs={
"genres": {
"terms": {
"field": "normalized_genre"
}
}
},
)
print(resp)
`
Ruby
response = client.search(
body: {
size: 0,
runtime_mappings: {
normalized_genre: {
type: 'keyword',
script: "\n String genre = doc['genre'].value;\n if (doc['product'].value.startsWith('Anthology')) {\n emit(genre + ' anthology');\n } else {\n emit(genre);\n }\n "
}
},
aggregations: {
genres: {
terms: {
field: 'normalized_genre'
}
}
}
}
)
puts response
Js
const response = await client.search({
size: 0,
runtime_mappings: {
normalized_genre: {
type: "keyword",
script:
"\n String genre = doc['genre'].value;\n if (doc['product'].value.startsWith('Anthology')) {\n emit(genre + ' anthology');\n } else {\n emit(genre);\n }\n ",
},
},
aggs: {
genres: {
terms: {
field: "normalized_genre",
},
},
},
});
console.log(response);
Console
GET /_search
{
"size": 0,
"runtime_mappings": {
"normalized_genre": {
"type": "keyword",
"script": """
String genre = doc['genre'].value;
if (doc['product'].value.startsWith('Anthology')) {
emit(genre + ' anthology');
} else {
emit(genre);
}
"""
}
},
"aggs": {
"genres": {
"terms": {
"field": "normalized_genre"
}
}
}
}
Console-Result
{
"aggregations": {
"genres": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "electronic",
"doc_count": 4
},
{
"key": "rock",
"doc_count": 3
},
{
"key": "electronic anthology",
"doc_count": 2
},
{
"key": "jazz",
"doc_count": 2
}
]
}
},
...
}
これは少し遅くなります。なぜなら、ランタイムフィールドは1つのフィールドではなく2つのフィールドにアクセスしなければならず、非ランタイムkeyword
フィールドで機能するいくつかの最適化を放棄しなければならないからです。速度が必要な場合は、normalized_genre
フィールドをインデックス化できます。
Filtering Values
バケットが作成される値をフィルタリングすることが可能です。これは、正規表現文字列または正確な値の配列に基づくinclude
およびexclude
パラメータを使用して行うことができます。さらに、include
句はpartition
式を使用してフィルタリングできます。
Filtering Values with regular expressions
Python
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"include": ".*sport.*",
"exclude": "water_.*"
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
include: '.*sport.*',
exclude: 'water_.*'
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"include": ".*sport.*",
"exclude": "water_.*"
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
include: ".*sport.*",
exclude: "water_.*",
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"include": ".*sport.*",
"exclude": "water_.*"
}
}
}
}
上記の例では、sport
という単語を含むすべてのタグのバケットが作成されますが、water_
で始まるものは除外されます(したがって、タグwater_sports
は集約されません)。include
正規表現は、集約される「許可された」値を決定し、exclude
は集約されるべきでない値を決定します。両方が定義されている場合、exclude
が優先され、つまり、include
が最初に評価され、その後exclude
が評価されます。
構文は正規表現クエリと同じです。
Filtering Values with exact values
正確な値に基づいて一致させるために、include
およびexclude
パラメータは、インデックスに見つかった用語を表す文字列の配列を単純に受け取ることができます:
Python
resp = client.search(
aggs={
"JapaneseCars": {
"terms": {
"field": "make",
"include": [
"mazda",
"honda"
]
}
},
"ActiveCarManufacturers": {
"terms": {
"field": "make",
"exclude": [
"rover",
"jensen"
]
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
"JapaneseCars": {
terms: {
field: 'make',
include: [
'mazda',
'honda'
]
}
},
"ActiveCarManufacturers": {
terms: {
field: 'make',
exclude: [
'rover',
'jensen'
]
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"JapaneseCars": {
"terms": {
"field": "make",
"include": [
"mazda",
"honda"
]
}
},
"ActiveCarManufacturers": {
"terms": {
"field": "make",
"exclude": [
"rover",
"jensen"
]
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
JapaneseCars: {
terms: {
field: "make",
include: ["mazda", "honda"],
},
},
ActiveCarManufacturers: {
terms: {
field: "make",
exclude: ["rover", "jensen"],
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"JapaneseCars": {
"terms": {
"field": "make",
"include": [ "mazda", "honda" ]
}
},
"ActiveCarManufacturers": {
"terms": {
"field": "make",
"exclude": [ "rover", "jensen" ]
}
}
}
}
Filtering Values with partitions
時には、単一のリクエスト/レスポンスペアで処理するにはユニークな用語が多すぎるため、分析を複数のリクエストに分割することが有用です。これは、クエリ時にフィールドの値を複数のパーティションにグループ化し、各リクエストで1つのパーティションのみを処理することで実現できます。このリクエストは、最近アクセスしていないアカウントを探しています:
Php
$params = [
'body' => [
'size' => 0,
'aggs' => [
'expired_sessions' => [
'terms' => [
'field' => 'account_id',
'include' => [
'partition' => 0,
'num_partitions' => 20,
],
'size' => 10000,
'order' => [
'last_access' => 'asc',
],
],
'aggs' => [
'last_access' => [
'max' => [
'field' => 'access_date',
],
],
],
],
],
],
];
$response = $client->search($params);
Python
resp = client.search(
size=0,
aggs={
"expired_sessions": {
"terms": {
"field": "account_id",
"include": {
"partition": 0,
"num_partitions": 20
},
"size": 10000,
"order": {
"last_access": "asc"
}
},
"aggs": {
"last_access": {
"max": {
"field": "access_date"
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
size: 0,
aggregations: {
expired_sessions: {
terms: {
field: 'account_id',
include: {
partition: 0,
num_partitions: 20
},
size: 10_000,
order: {
last_access: 'asc'
}
},
aggregations: {
last_access: {
max: {
field: 'access_date'
}
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"size": 0,
"aggs": {
"expired_sessions": {
"terms": {
"field": "account_id",
"include": {
"partition": 0,
"num_partitions": 20
},
"size": 10000,
"order": {
"last_access": "asc"
}
},
"aggs": {
"last_access": {
"max": {
"field": "access_date"
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
size: 0,
aggs: {
expired_sessions: {
terms: {
field: "account_id",
include: {
partition: 0,
num_partitions: 20,
},
size: 10000,
order: {
last_access: "asc",
},
},
aggs: {
last_access: {
max: {
field: "access_date",
},
},
},
},
},
});
console.log(response);
Console
GET /_search
{
"size": 0,
"aggs": {
"expired_sessions": {
"terms": {
"field": "account_id",
"include": {
"partition": 0,
"num_partitions": 20
},
"size": 10000,
"order": {
"last_access": "asc"
}
},
"aggs": {
"last_access": {
"max": {
"field": "access_date"
}
}
}
}
}
}
このリクエストは、顧客アカウントのサブセットの最終ログイン日を見つけています。なぜなら、長い間見られていない顧客アカウントを期限切れにしたいからです。num_partitions
設定は、ユニークなaccount_idsが20のパーティション(0から19)に均等に整理されることを要求しています。このリクエストのpartition
設定は、パーティション0に該当するaccount_idsのみを考慮するようにフィルタリングします。次のリクエストでは、パーティション1、次に2などを要求して、期限切れアカウントの分析を完了する必要があります。
返される結果の数に関するsize
設定は、num_partitions
と調整する必要があります。この特定のアカウント期限切れの例では、size
とnum_partitions
の値をバランスさせるプロセスは次のようになります:
- 1.
cardinality
集約を使用して、ユニークなaccount_id値の合計数を推定します。 - 2. 1)の数をより管理しやすいチャンクに分割するために
num_partitions
の値を選択します。 - 3. 各パーティションから取得したいレスポンスの数のために
size
値を選択します。 - 4. テストリクエストを実行します。
サーキットブレーカーエラーが発生した場合、1つのリクエストでやりすぎているため、num_partitions
を増やす必要があります。リクエストが成功したが、日付でソートされたテストレスポンスの最後のアカウントIDがまだ期限切れにしたいアカウントである場合、興味のあるアカウントが欠落している可能性があり、数値を低く設定しすぎている可能性があります。私たちは、
- 各パーティションでより多くの結果を返すために
size
パラメータを増やす(メモリに重い可能性があります)か、 - 各リクエストで考慮するアカウントを減らすために
num_partitions
を増やす(より多くのリクエストを行う必要があるため、全体の処理時間が増加する可能性があります)
最終的には、単一のリクエストを処理するために必要なElasticsearchリソースと、クライアントアプリケーションがタスクを完了するために発行しなければならないリクエストのボリュームとのバランスを取ることになります。
パーティションは、exclude
パラメータと一緒に使用することはできません。
Multi-field terms aggregation
terms
集約は、同じドキュメント内の複数のフィールドから用語を収集することをサポートしていません。その理由は、terms
aggが文字列の用語値自体を収集するのではなく、グローバルオーディナルを使用してフィールド内のすべてのユニークな値のリストを生成するためです。グローバルオーディナルは、複数のフィールドにわたっては不可能な重要なパフォーマンスブーストをもたらします。
複数のフィールドにわたってterms
aggを実行するために使用できる3つのアプローチがあります:
- スクリプト
- 複数のフィールドから用語を取得するためにスクリプトを使用します。これにより、グローバルオーディナルの最適化が無効になり、単一フィールドから用語を収集するよりも遅くなりますが、検索時にこのオプションを実装する柔軟性が得られます。
copy_to
フィールド- 2つ以上のフィールドから用語を収集したいことが事前にわかっている場合は、
copy_to
を使用してインデックス時に両方のフィールドの値を含む新しい専用フィールドを作成します。この単一フィールドで集約でき、グローバルオーディナルの最適化の恩恵を受けます。 multi_terms
集約- 複数のフィールドから用語を組み合わせて複合キーを作成するためにマルチターム集約を使用します。これにより、グローバルオーディナルが無効になり、単一フィールドから用語を収集するよりも遅くなりますが、スクリプトを使用するよりも速く、柔軟性は低くなります。
Collect mode
子集約の計算を遅延させる
ユニークな用語が多く、必要な結果が少ないフィールドでは、親レベルの集約がプルーニングされるまで子集約の計算を遅らせる方が効率的です。通常、集約ツリーのすべてのブランチは、1つの深さ優先パスで展開され、その後にプルーニングが行われます。いくつかのシナリオでは、これは非常に無駄であり、メモリ制約に達する可能性があります。問題のシナリオの例は、映画データベースに対して最も人気のある10人の俳優とその5人の最も一般的な共演者をクエリすることです:
Python
resp = client.search(
aggs={
"actors": {
"terms": {
"field": "actors",
"size": 10
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
actors: {
terms: {
field: 'actors',
size: 10
},
aggregations: {
costars: {
terms: {
field: 'actors',
size: 5
}
}
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"actors": {
"terms": {
"field": "actors",
"size": 10
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
actors: {
terms: {
field: "actors",
size: 10,
},
aggs: {
costars: {
terms: {
field: "actors",
size: 5,
},
},
},
},
},
});
console.log(response);
用語の集約
ユニークな値ごとにバケットが動的に構築されるマルチバケット値ソースベースの集約です。
例:
用語の集約
ユニークな値ごとにバケットが動的に構築されるマルチバケット値ソースベースの集約です。
例:
ルビー
response = client.search(
body: {
aggregations: {
actors: {
terms: {
field: 'actors',
size: 10,
collect_mode: 'breadth_first'
},
aggregations: {
costars: {
terms: {
field: 'actors',
size: 5
}
}
}
}
}
}
)
puts response
ゴー
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"actors": {
"terms": {
"field": "actors",
"size": 10,
"collect_mode": "breadth_first"
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
actors: {
terms: {
field: "actors",
size: 10,
collect_mode: "breadth_first",
},
aggs: {
costars: {
terms: {
field: "actors",
size: 5,
},
},
},
},
},
});
console.log(response);
コンソール
GET /_search
{
"aggs": {
"actors": {
"terms": {
"field": "actors",
"size": 10,
"collect_mode": "breadth_first"
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
}
}
可能な値は breadth_first と depth_first です |
breadth_first
モードを使用する場合、最上位のバケットに該当するドキュメントのセットは、後続のリプレイのためにキャッシュされるため、これを行う際にはメモリのオーバーヘッドが発生し、これは一致するドキュメントの数に対して線形です。order
パラメータは、breadth_first
設定を使用する際に、子集計からのデータを参照するために引き続き使用できます - 親集計は、この子集計が他の子集計の前に呼び出される必要があることを理解しています。
top_hits
のようなネストされた集計は、breadth_first
コレクションモードを使用する集計の下でスコア情報にアクセスする必要があるため、2回目のパスでクエリをリプレイする必要がありますが、最上位のバケットに属するドキュメントのみに対してのみ行われます。
実行ヒント
用語集計を実行するための異なるメカニズムがあります:
- バケットごとにデータを集計するためにフィールド値を直接使用すること (
map
) - フィールドのグローバルオーディナルを使用し、グローバルオーディナルごとに1つのバケットを割り当てること (
global_ordinals
)
Elasticsearchは、合理的なデフォルトを持つように努めているため、これは一般的に構成する必要がないものです。
global_ordinals
は keyword
フィールドのデフォルトオプションであり、グローバルオーディナルを使用してバケットを動的に割り当てるため、メモリ使用量は集計スコープに含まれるドキュメントの値の数に対して線形です。
map
は、非常に少数のドキュメントがクエリに一致する場合にのみ考慮されるべきです。そうでなければ、オーディナルベースの実行モードは大幅に高速です。デフォルトでは、map
はスクリプトで集計を実行する場合にのみ使用されます。スクリプトにはオーディナルがないためです。
Python
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"execution_hint": "map"
}
}
},
)
print(resp)
Ruby
response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
execution_hint: 'map'
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"execution_hint": "map"
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
execution_hint: "map",
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"execution_hint": "map"
}
}
}
}
可能な値はmap 、global_ordinals です。 |
Elasticsearchは、適用できない場合はこの実行ヒントを無視し、これらのヒントに対して後方互換性の保証はありません。
Missing value
#### Python
``````python
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"missing": "N/A"
}
}
},
)
print(resp)
`
Ruby
response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
missing: 'N/A'
}
}
}
}
)
puts response
Go
res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"missing": "N/A"
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err)
Js
const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
missing: "N/A",
},
},
},
});
console.log(response);
Console
GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"missing": "N/A"
}
}
}
}
tags フィールドに値がないドキュメントは、N/A の値を持つドキュメントと同じバケットに入ります。 |
フィールドタイプの混合
複数のインデックスで集計する場合、集計されるフィールドのタイプはすべてのインデックスで同じでない可能性があります。一部のタイプは互換性があります (integer
と long
または float
と double
) が、タイプが小数と非小数の数の混合である場合、用語集計は非小数の数を小数の数に昇格させます。これにより、バケット値の精度が失われる可能性があります。
トラブルシューティング
バイトのフォーマットに失敗しました
複数のインデックスで用語集計(または他の集計、実際には通常は用語)を実行していると、
「バイトのフォーマットに失敗しました…」で始まるエラーが発生することがあります。これは通常、集計されるフィールドのマッピングタイプが同じでない2つのインデックスが原因です。
明示的な value_type
を使用してください マッピングを修正するのが最善ですが、インデックスの1つでフィールドが未マップの場合、この問題を回避することができます。value_type
パラメータを設定することで、未マップのフィールドを正しいタイプに強制することで問題を解決できます。
Python
resp = client.search(
aggs={
"ip_addresses": {
"terms": {
"field": "destination_ip",
"missing": "0.0.0.0",
"value_type": "ip"
}
}
},
)
print(resp)
ルビー
response = client.search(
body: {
aggregations: {
ip_addresses: {
terms: {
field: 'destination_ip',
missing: '0.0.0.0',
value_type: 'ip'
}
}
}
}
)
puts response
Js
const response = await client.search({
aggs: {
ip_addresses: {
terms: {
field: "destination_ip",
missing: "0.0.0.0",
value_type: "ip",
},
},
},
});
console.log(response);
コンソール
GET /_search
{
"aggs": {
"ip_addresses": {
"terms": {
"field": "destination_ip",
"missing": "0.0.0.0",
"value_type": "ip"
}
}
}
}