Агрегация Terms
Агрегация, основанная на источнике значений для нескольких корзин, где корзины динамически создаются — по одному на каждое уникальное значение.
Пример:
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre"
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre'
}
}
}
}
)
puts response const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"genres": {
"terms": { "field": "genre" }
}
}
} Ответ:
{
...
"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 возвращает только лучшие термины; это число равно сумме подсчётов документов для всех корзин, которые не входят в ответ | |
| список лучших корзин, значение |
Поле для field может быть типа Keyword, Numeric, IP, Boolean или Binary.
По умолчанию вы не можете выполнить агрегацию terms на поле text. Используйте вложенное подполе keyword fields вместо него. В качестве альтернативы, вы можете включить параметр индексирования fielddata для поля text, чтобы создать корзины для анализируемых терминов поля. Включение fielddata может значительно увеличить использование памяти.
Размер
По умолчанию агрегация terms возвращает десять лучших терминов с наибольшим количеством документов. Используйте параметр size, чтобы вернуть больше терминов, до предела search.max_buckets.
Если ваши данные содержат 100 или 1000 уникальных терминов, вы можете увеличить size агрегации terms, чтобы вернуть их все. Если у вас больше уникальных терминов и вам нужны все, используйте агрегацию composite вместо нее.
Большие значения size требуют больше памяти для вычислений и приближают всю агрегацию к пределу max_buckets. Вы узнаете, что значение слишком велико, если запрос завершится сообщением об ошибке max_buckets.
Размер фрагмента
Для получения более точных результатов агрегация terms извлекает больше, чем лучшие size термины с каждого фрагмента. Она извлекает лучшие shard_size термины, значение по умолчанию — size * 1.5 + 10.
Это необходимо для случая, когда один термин имеет много документов на одном фрагменте, но находится ниже порога size на всех других фрагментах. Если каждый фрагмент возвращал только size терминов, агрегация вернула бы неполный счёт документов для термина. Поэтому terms возвращает больше терминов, пытаясь поймать пропущенные термины. Это помогает, но всё ещё существует вероятность возврата неполного подсчёта документов для термина. Требуется термин с более неоднородными подсчётами документов на фрагментах.
Вы можете увеличить shard_size, чтобы лучше учесть эти неоднородные подсчёты документов и улучшить точность выбора лучших терминов. Значительно проще увеличить shard_size, чем size. Тем не менее, это всё ещё требует больше байтов по сети и ожидания в памяти на координационном узле.
Это руководство применимо только если вы используете сортировку по умолчанию агрегации terms. Если вы сортируете по чему-либо, кроме подсчёта документов в порядке убывания, см. Порядок.
shard_size не может быть меньше size (так как это не имеет смысла). В этом случае Elasticsearch переопределит его и установит значение, равное size.
Ошибка подсчета документов
Даже с большим значением shard_size, значения doc_count для агрегации terms могут быть приблизительными. В результате любые вложенные агрегации в агрегации terms также могут быть приблизительными.
sum_other_doc_count — количество документов, которые не вошли в лучшие size термины. Если это значение больше, чем 0, можно с уверенностью сказать, что агрегация terms отбросила некоторые корзины, либо потому, что они не поместились в size на координационном узле, либо потому, что они не поместились в shard_size на узле данных.
Ошибка подсчета документов на каждую корзину
Если вы установили параметр show_term_doc_count_error в значение true, агрегация terms будет включать doc_count_error_upper_bound, что является верхней границей ошибки в подсчете doc_count, возвращаемом каждым фрагментом. Это сумма размера самой большой корзины на каждом фрагменте, которая не поместилась в shard_size.
Проще говоря, представьте, что существует одна корзина, которая очень велика на одном фрагменте и чуть за пределами shard_size на всех других фрагментах. В этом случае агрегация terms вернёт корзину, потому что она большая, но пропустит данные из многих документов на фрагментах, где термин опустился ниже порога shard_size. doc_count_error_upper_bound — максимальное количество этих пропущенных документов.
resp = client.search(
aggs={
"products": {
"terms": {
"field": "product",
"size": 5,
"show_term_doc_count_error": True
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
products: {
terms: {
field: 'product',
size: 5,
show_term_doc_count_error: true
}
}
}
}
)
puts response 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) const response = await client.search({
aggs: {
products: {
terms: {
field: "product",
size: 5,
show_term_doc_count_error: true,
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"products": {
"terms": {
"field": "product",
"size": 5,
"show_term_doc_count_error": true
}
}
}
} Эти ошибки могут быть вычислены только таким образом, когда термины упорядочены по убыванию подсчёта документов. Когда агрегация отсортирована по самим значениям терминов (по возрастанию или убыванию), в подсчёте документов ошибки нет, так как если фрагмент не возвращает конкретный термин, который появляется в результатах с другого фрагмента, значит его нет в индексе этого фрагмента. Когда агрегация сортируется по вложенной агрегации или в порядке возрастания подсчёта документов, ошибка в подсчётах документов не может быть определена и получает значение -1, чтобы указать на это.
Сортировка
По умолчанию агрегация terms сортирует термины по убыванию количества документов _count. Это приводит к ограниченной ошибке подсчёта документов, которую Elasticsearch может сообщить.
Вы можете использовать параметр order для указания другой сортировки, но мы этого не рекомендуем. Очень легко создать порядок сортировки терминов, который вернёт неправильные результаты, и не очевидно, когда это происходит. Изменяйте это только с осторожностью.
Особенно избегайте использования "order": { "_count": "asc" }. Если вам нужно найти редкие термины, используйте агрегацию rare_terms вместо неё. Из-за того, как агрегация terms получает термины из фрагментов, сортировка по возрастанию количества документов часто приводит к неточным результатам.
Сортировка по значению термина
В этом случае корзины сортируются по фактическим значениям терминов, таким как лексикографический порядок для ключевых слов или числовой для чисел. Эта сортировка безопасна как в возрастающем, так и в убывающем направлениях и даёт точные результаты.
Пример сортировки корзин в алфавитном порядке по возрастанию:
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"_key": "asc"
}
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
_key: 'asc'
}
}
}
}
}
)
puts response res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": {
"_key": "asc"
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err) const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
order: {
_key: "asc",
},
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "_key": "asc" }
}
}
}
} Сортировка по под-агрегации
Сортировка по под-агрегации обычно приводит к неправильной сортировке из-за того, как агрегация terms получает результаты из фрагментов.
Есть два случая, когда сортировка по под-агрегации безопасна и возвращает правильные результаты: сортировка по максимальному значению в убывающем порядке или сортировка по минимальному значению в возрастающем порядке. Эти подходы работают, потому что они соответствуют поведению под-агрегаций. То есть, если вы ищете наибольшее максимальное значение или наименьшее минимальное значение, глобальный ответ (из объединённых фрагментов) должен быть включён в один из ответов локального фрагмента. Напротив, наименьшее максимальное значение и наибольшее минимальное значение не будут вычислены точно.
Обратите также внимание, что в этих случаях сортировка корректна, но количество документов и под-агрегации, не связанные с сортировкой, могут иметь ошибки (и Elasticsearch не вычисляет границу для этих ошибок).
Сортировка корзин по под-агрегации с единственным значением (определяется именем агрегации):
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"max_play_count": "desc"
}
},
"aggs": {
"max_play_count": {
"max": {
"field": "play_count"
}
}
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
max_play_count: 'desc'
}
},
aggregations: {
max_play_count: {
max: {
field: 'play_count'
}
}
}
}
}
}
)
puts response 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) 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); GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "max_play_count": "desc" }
},
"aggs": {
"max_play_count": { "max": { "field": "play_count" } }
}
}
}
} Сортировка корзин по под-агрегации с несколькими значениями (определяется именем агрегации):
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"playback_stats.max": "desc"
}
},
"aggs": {
"playback_stats": {
"stats": {
"field": "play_count"
}
}
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
'playback_stats.max' => 'desc'
}
},
aggregations: {
playback_stats: {
stats: {
field: 'play_count'
}
}
}
}
}
}
)
puts response 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) 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); GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "playback_stats.max": "desc" }
},
"aggs": {
"playback_stats": { "stats": { "field": "play_count" } }
}
}
}
} Агрегации-трубы не могут использоваться для сортировки
Агрегации-трубы выполняются на фазе редукции после того, как все другие агрегации уже завершены. По этой причине они не могут использоваться для сортировки.
Также возможно отсортировать корзины на основе "более глубокой" агрегации в иерархии. Это поддерживается, если пути агрегаций являются типами с одной корзиной, где последняя агрегация в пути может быть либо типом с одной корзиной, либо метрической. Если это тип с одной корзиной, порядок определяется количеством документов в корзине (т.е. doc_count), если это метрическая, применяются те же правила, что и выше (где путь должен указывать имя метрики для сортировки в случае многозначной метрической агрегации, а в случае однозначной метрической агрегации сортировка будет применена к этому значению).
Путь должен быть определён в следующем формате:
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> ] ;
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) 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 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) 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); 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" } }
}
}
}
}
}
} Вышеупомянутое отсортирует корзины стран исполнителей по среднему количеству воспроизведений среди песен в стиле рок.
Для сортировки корзин по нескольким критериям можно использовать массив критериев сортировки, например:
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) 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 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) 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); 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 в убывающем порядке.
В случае, если две корзины имеют одинаковые значения для всех критериев сортировки, значение термина корзины используется в качестве дополнительного критерия в алфавитном порядке по возрастанию, чтобы избежать непредсказуемой сортировки корзин.
Сортировка по возрастанию количества
Сортировка терминов по возрастанию количества документов _count приводит к неограниченной ошибке, которую Elasticsearch не может точно сообщить. Поэтому мы настоятельно не рекомендуем использовать "order": { "_count": "asc" }, как показано в следующем примере:
resp = client.search(
aggs={
"genres": {
"terms": {
"field": "genre",
"order": {
"_count": "asc"
}
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
genres: {
terms: {
field: 'genre',
order: {
_count: 'asc'
}
}
}
}
}
)
puts response res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": {
"_count": "asc"
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err) const response = await client.search({
aggs: {
genres: {
terms: {
field: "genre",
order: {
_count: "asc",
},
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"genres": {
"terms": {
"field": "genre",
"order": { "_count": "asc" }
}
}
}
} Минимальное количество документов
Возможна настройка возврата только тех терминов, которые соответствуют более чем заданному числу совпадений, используя параметр min_doc_count:
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"min_doc_count": 10
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
min_doc_count: 10
}
}
}
}
)
puts response 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) const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
min_doc_count: 10,
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"min_doc_count": 10
}
}
}
} Вышеприведенная агрегация вернёт только те теги, которые были найдены в 10 или более совпадениях. Значение по умолчанию — 1.
Термины собираются и упорядочиваются на уровне фрагмента, а затем объединяются с терминами, собранными из других фрагментов на втором этапе. Однако фрагмент не имеет информации о глобальном количестве документов. Решение о добавлении термина в список кандидатов зависит только от порядка, вычисленного на фрагменте, используя локальные частоты фрагмента. Критерий min_doc_count применяется только после объединения статистических данных локальных терминов всех фрагментов. Таким образом, решение о добавлении термина в список кандидатов принимается, не будучи уверенным в том, достигнет ли этот термин фактически требуемого значения min_doc_count. Это может привести к тому, что многие (глобально) часто встречающиеся термины будут отсутствовать в конечном результате, если низкочастотные термины заполнили списки кандидатов. Чтобы избежать этого, параметр shard_size можно увеличить, чтобы разрешить больше кандидатов на фрагментах. Однако это увеличивает потребление памяти и трафик сети.
shard_min_doc_count
Параметр shard_min_doc_count регулирует уверенность фрагмента в том, должен ли термин быть добавлен в список кандидатов или нет, относительно min_doc_count. Термины будут рассматриваться только в том случае, если их локальная частота на фрагменте в данном наборе выше, чем shard_min_doc_count. Если ваш словарь содержит много низкочастотных терминов, и вы не заинтересованы в них (например, в ошибках написания), вы можете установить параметр shard_min_doc_count для фильтрации терминов-кандидатов на уровне фрагмента, которые с достаточной уверенностью не достигнут требуемого значения min_doc_count даже после объединения локальных подсчётов. shard_min_doc_count установлен по умолчанию в значение 0 и не оказывает никакого влияния, если его не задать явно.
Установив min_doc_count=0, также будут возвращены корзины для терминов, которые не соответствуют ни одному совпадению. Однако некоторые из возвращённых терминов, у которых количество документов равно нулю, могут относиться только к удалённым документам или документам других типов, поэтому нет гарантии, что запрос match_all найдёт положительное количество документов для этих терминов.
Если сортировка не выполняется по doc_count в порядке убывания, большие значения min_doc_count могут вернуть количество корзин, меньшее, чем size, так как из фрагментов было собрано недостаточно данных. Пропущенные корзины можно получить, увеличив значение shard_size. Слишком высокое значение shard_min_doc_count приведёт к фильтрации терминов на уровне фрагмента. Это значение должно быть значительно меньше, чем min_doc_count/#shards.
Скрипт
Используйте поле runtime, если данные в ваших документах не точно соответствуют тому, что вы хотите агрегировать. Например, если «антологии» должны быть в особой категории, вы можете выполнить следующее:
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) 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 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); 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"
}
}
}
} Что будет выглядеть так:
{
"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
}
]
}
},
...
} Это немного медленнее, потому что поле runtime должно обращаться к двум полям вместо одного, и потому что некоторые оптимизации, работающие с не-runtime-keyword полями, недоступны для runtime-keyword полей. Если вам нужна скорость, вы можете проиндексировать поле normalized_genre.
Фильтрация значений
Возможна фильтрация значений, для которых будут созданы корзины. Это можно сделать, используя параметры include и exclude, которые основаны на строках регулярных выражений или массивах точных значений. Кроме того, клаузы include могут фильтровать с использованием выражений partition.
Фильтрация значений с помощью регулярных выражений
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"include": ".*sport.*",
"exclude": "water_.*"
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
include: '.*sport.*',
exclude: 'water_.*'
}
}
}
}
)
puts response 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) const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
include: ".*sport.*",
exclude: "water_.*",
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"include": ".*sport.*",
"exclude": "water_.*"
}
}
}
} В приведенном выше примере, корзины будут созданы для всех тегов, содержащих слово sport, за исключением тех, которые начинаются с water_ (так что тег water_sports не будет агрегирован). Регулярное выражение include определяет, какие значения «разрешены» для агрегирования, а exclude определяет значения, которые не должны быть агрегированы. Если оба значения определены, exclude имеет приоритет, то есть include оценивается первой, а затем exclude.
Синтаксис такой же, как в запросах регулярных выражений.
Фильтрация значений с помощью точных значений
Для соответствия по точным значениям параметры include и exclude могут просто принимать массив строк, представляющих термины в том виде, как они найдены в индексе:
resp = client.search(
aggs={
"JapaneseCars": {
"terms": {
"field": "make",
"include": [
"mazda",
"honda"
]
}
},
"ActiveCarManufacturers": {
"terms": {
"field": "make",
"exclude": [
"rover",
"jensen"
]
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
"JapaneseCars": {
terms: {
field: 'make',
include: [
'mazda',
'honda'
]
}
},
"ActiveCarManufacturers": {
terms: {
field: 'make',
exclude: [
'rover',
'jensen'
]
}
}
}
}
)
puts response 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) const response = await client.search({
aggs: {
JapaneseCars: {
terms: {
field: "make",
include: ["mazda", "honda"],
},
},
ActiveCarManufacturers: {
terms: {
field: "make",
exclude: ["rover", "jensen"],
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"JapaneseCars": {
"terms": {
"field": "make",
"include": [ "mazda", "honda" ]
}
},
"ActiveCarManufacturers": {
"terms": {
"field": "make",
"exclude": [ "rover", "jensen" ]
}
}
}
} Фильтрация значений с помощью разделов
Иногда существует слишком много уникальных терминов для обработки в одной паре запрос/ответ, поэтому полезно разбить анализ на несколько запросов. Этого можно достичь путём группировки значений поля в несколько разделов во время запроса и обработкой только одного раздела в каждом запросе. Рассмотрим этот запрос, который ищет учётные записи, которые недавно не регистрировали доступ:
$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); 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) 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 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) 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); 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 были равномерно распределены по двадцати разделам (с 0 до 19). А параметр partition в этом запросе фильтрует, чтобы учитывать только account_ids, попадающие в раздел 0. Последующие запросы должны запрашивать разделы 1, затем 2 и т.д., чтобы завершить анализ аннулирования учётных записей.
Обратите внимание, что значение size для числа возвращаемых результатов необходимо настраивать в сочетании с num_partitions. В этом примере аннулирования учётных записей процесс балансировки значений для size и num_partitions будет следующим:
- Используйте агрегацию
cardinalityдля оценки общего количества уникальных значений account_id. - Выберите значение для
num_partitions, чтобы разбить число из 1) на более управляемые части. - Выберите значение
sizeдля количества ответов, которые вы хотите получить от каждого раздела. - Запустите тестовый запрос.
Если возникает ошибка circuit-breaker, значит, мы пытаемся сделать слишком много в одном запросе, и необходимо увеличить значение num_partitions. Если запрос был успешным, но последний идентификатор учётной записи в упорядоченном по дате тестовом ответе всё ещё является учётной записью, которую мы можем захотеть аннулировать, значит, мы, возможно, пропустили учётные записи, которые нас интересуют, и задали наши значения слишком низкими. Мы должны либо
- увеличить параметр
size, чтобы вернуть больше результатов за раздел (может сильно нагрузить память), или - увеличить значение
num_partitions, чтобы рассмотреть меньше учётных записей за запрос (может увеличить общее время обработки, так как нам нужно сделать больше запросов).
В конечном счёте, это баланс между управлением ресурсами Elasticsearch, необходимыми для обработки одного запроса, и объёмом запросов, которые приложение-клиент должно выполнить для завершения задачи.
Разделы нельзя использовать вместе с параметром exclude.
Агрегирование терминов по нескольким полям
Агрегация terms не поддерживает сбор терминов из нескольких полей в одном документе. Причина в том, что агрегация terms не собирает сами значения строковых терминов, а вместо этого использует глобальные ординалы для создания списка всех уникальных значений в поле. Глобальные ординалы приводят к важному повышению производительности, которое невозможно при работе с несколькими полями.
Существует три подхода, которые вы можете использовать для выполнения агрегации terms по нескольким полям:
- Скрипт
- Используйте скрипт для извлечения терминов из нескольких полей. Это отключает оптимизацию глобальных ординалов и будет медленнее, чем сбор терминов из одного поля, но предоставляет гибкость для реализации этого варианта во время поиска.
- Поле
copy_to - Если вам заранее известно, что вы хотите собрать термины из двух или более полей, используйте
copy_toв вашем отображении, чтобы создать новое специальное поле во время индексирования, содержащее значения из обоих полей. Вы можете агрегировать по этому единственному полю, что даст выгоду от оптимизации глобальных ординалов. - Агрегация
multi_terms - Используйте агрегацию multi_terms для объединения терминов из нескольких полей в составной ключ. Это также отключает глобальные ординалы и будет медленнее, чем сбор терминов из одного поля. Она быстрее, но менее гибкая, чем использование скрипта.
Режим сбора
Отложенное вычисление дочерних агрегаций
Для полей с большим количеством уникальных терминов и малым количеством требуемых результатов может быть эффективнее отложить вычисление дочерних агрегаций до тех пор, пока не будут отсечены верхние родительские агрегации. Обычно все ветви дерева агрегации раскрываются в одном проходе по принципу «глубина в ширину», и только после этого происходит отсечение. В некоторых сценариях это может быть очень расточительно и может привести к ограничениям по памяти. Пример проблемного сценария — поиск в базе данных фильмов 10 самых популярных актеров и их 5 самых частых партнеров по съёмкам:
resp = client.search(
aggs={
"actors": {
"terms": {
"field": "actors",
"size": 10
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
actors: {
terms: {
field: 'actors',
size: 10
},
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
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err) const response = await client.search({
aggs: {
actors: {
terms: {
field: "actors",
size: 10,
},
aggs: {
costars: {
terms: {
field: "actors",
size: 5,
},
},
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"actors": {
"terms": {
"field": "actors",
"size": 10
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
}
} Даже если количество актеров относительно невелико, а мы хотим только 50 результатов, во время вычислений происходит комбинаторный взрыв корзин — один актер может создать n² корзин, где n — количество актеров. Разумный вариант — сначала определить 10 самых популярных актеров, а затем рассмотреть топ-партнёров по съёмкам для этих 10 актеров. Этот альтернативный подход называется режимом breadth_first сбора, в отличие от режима depth_first.
Режим breadth_first является по умолчанию для полей с кардинальностью, большей, чем запрошенная величина, или когда кардинальность неизвестна (например, числовые поля или скрипты). Возможна переопределенная по умолчанию эвристика и предоставление режима сбора непосредственно в запросе:
resp = client.search(
aggs={
"actors": {
"terms": {
"field": "actors",
"size": 10,
"collect_mode": "breadth_first"
},
"aggs": {
"costars": {
"terms": {
"field": "actors",
"size": 5
}
}
}
}
},
)
print(resp) 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) 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 набор документов, попадающих в верхние корзины, кэшируется для последующего повторного воспроизведения, поэтому существует затраты по памяти, линейно зависящие от количества соответствующих документов. Обратите внимание, что параметр order по-прежнему может использоваться для ссылки на данные из дочерней агрегации при использовании настройки breadth_first — родительская агрегация понимает, что эта дочерняя агрегация должна быть вызвана первой, прежде чем любые другие дочерние агрегации.
Вложенные агрегации, такие как top_hits, требующие доступа к информации о баллах в рамках агрегации, использующей режим сбора breadth_first, должны повторно воспроизвести запрос на втором проходе, но только для документов, принадлежащих к верхним корзинам.
Указание выполнения
Существуют различные механизмы выполнения агрегаций по терминам:
- используя значения полей напрямую для агрегации данных по каждой корзине (
map) - используя глобальные ординалы поля и выделяя одну корзину на каждый глобальный ординал (
global_ordinals)
Elasticsearch пытается использовать разумные значения по умолчанию, поэтому обычно это не требует настройки.
global_ordinals — это вариант по умолчанию для keyword поля, он использует глобальные ординалы для динамического выделения корзин, поэтому использование памяти линейно зависит от количества значений документов, входящих в область действия агрегации.
map следует рассматривать только в том случае, если очень мало документов соответствуют запросу. В противном случае режим выполнения на основе ординалов значительно быстрее. По умолчанию map используется только при выполнении агрегации по скриптам, поскольку у них нет ординалов.
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"execution_hint": "map"
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
execution_hint: 'map'
}
}
}
}
)
puts response res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"execution_hint": "map"
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err) const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
execution_hint: "map",
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"execution_hint": "map"
}
}
}
} | Возможные значения — |
Обратите внимание, что Elasticsearch проигнорирует это указание выполнения, если оно не применимо, и нет гарантии обратной совместимости этих указаний.
Отсутствующее значение
Параметр missing определяет, как следует обрабатывать документы, у которых отсутствует значение. По умолчанию они будут игнорироваться, но также можно рассматривать их как имеющие значение.
resp = client.search(
aggs={
"tags": {
"terms": {
"field": "tags",
"missing": "N/A"
}
}
},
)
print(resp) response = client.search(
body: {
aggregations: {
tags: {
terms: {
field: 'tags',
missing: 'N/A'
}
}
}
}
)
puts response res, err := es.Search(
es.Search.WithBody(strings.NewReader(`{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"missing": "N/A"
}
}
}
}`)),
es.Search.WithPretty(),
)
fmt.Println(res, err) const response = await client.search({
aggs: {
tags: {
terms: {
field: "tags",
missing: "N/A",
},
},
},
});
console.log(response); GET /_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"missing": "N/A"
}
}
}
} | Документы без значения в поле |
Смешивание типов полей
При агрегации по нескольким индексам тип агрегируемого поля может отличаться в разных индексах. Некоторые типы совместимы друг с другом (integer и long или float и double), но когда типы представляют собой смесь десятичных и не десятичных чисел, агрегация по терминам преобразует не десятичные числа в десятичные. Это может привести к потере точности значений корзины.
Поиск неисправностей
Ошибка при форматировании байтов
При запуске агрегации по терминам (или другой агрегации, но на практике обычно по терминам) по нескольким индексам может появиться ошибка, начинающаяся с "Ошибка при форматировании байтов…". Обычно это происходит из-за того, что два индекса не имеют одинакового типа отображения для агрегируемого поля.
Используйте явное value_type Хотя лучше исправить отображения, вы можете обойти эту проблему, если поле не отображается в одном из индексов. Установка параметра value_type может решить проблему, принудительно преобразуя неописанное поле в правильный тип.
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 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"
}
}
}
}
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-aggregations-bucket-terms-aggregation.html