Метрические и бакетные агрегации
До сих пор Elasticsearch работал у нас как поисковик: принимал текст или вектор, возвращал документы. Но те же данные можно использовать совсем иначе — считать статистику, строить распределения, группировать по категориям. Всё это делают агрегации (aggregations).
По-простому: агрегации в ES — это то, что GROUP BY плюс агрегатные функции делают в SQL, только прямо поверх тех же индексов, по которым вы ищете.
Все агрегации делятся на два базовых класса:
- Metric-агрегации — вычисляют одно число (или несколько) по набору документов: среднее, сумму, максимум и т. д.
- Bucket-агрегации — делят документы на группы («бакеты»). Каждый бакет — подмножество документов. Внутрь бакетов можно вложить метрики или другие бакеты.
flowchart TD
A["Aggregations"] --> B["Metric\nВозвращают числовое значение"]
A --> C["Bucket\nДелят документы на группы"]
B --> B1["avg · sum · min · max"]
B --> B2["stats\n(все пять метрик сразу)"]
B --> B3["cardinality\n(≈ уникальных значений)"]
C --> C1["terms\nпо значению keyword-поля"]
C --> C2["range\nпо числовым диапазонам"]
C --> C3["date_histogram\nпо временным интервалам"]
C -.->|"+ sub-aggregations"| BЗапрос только за агрегациями: "size": 0
Когда вам нужна аналитика, а не сами документы, добавьте в запрос "size": 0. Тогда ES вернёт только блок "aggregations" и не потратит время на сериализацию документов:
GET /orders/_search
{
"size": 0,
"aggs": {
"имя_агрегации": { ... }
}
}Без "size": 0 вы получите и 10 документов (по умолчанию), и агрегации — двойная работа без пользы.
> Ключ для объявления агрегаций — "aggs" (или полная форма "aggregations"). Оба варианта работают одинаково.
Metric-агрегации: числа по документам
#### avg, sum, min, max
Стандартные математические функции. Пример: считаем сразу четыре метрики по полю price в одном запросе:
GET /orders/_search
{
"size": 0,
"aggs": {
"средняя_цена": { "avg": { "field": "price" } },
"суммарная_цена": { "sum": { "field": "price" } },
"минимальная": { "min": { "field": "price" } },
"максимальная": { "max": { "field": "price" } }
}
}Имена агрегаций ("средняя_цена" и т. д.) — произвольные строки. ES использует их как ключи в ответе:
{
"aggregations": {
"средняя_цена": { "value": 1240.5 },
"суммарная_цена": { "value": 248100.0 },
"минимальная": { "value": 199.0 },
"максимальная": { "value": 9999.0 }
}
}#### stats — всё сразу
Если нужны $\min$, $\max$, $\text{avg}$, $\text{sum}$ и $\text{count}$ одним запросом — используйте stats:
GET /orders/_search
{
"size": 0,
"aggs": {
"статистика_цены": { "stats": { "field": "price" } }
}
}Ответ:
{
"aggregations": {
"статистика_цены": {
"count": 200,
"min": 199.0,
"max": 9999.0,
"avg": 1240.5,
"sum": 248100.0
}
}
}stats — удобный инструмент для быстрой диагностики: один вызов вместо четырёх отдельных агрегаций.
#### cardinality — количество уникальных значений
Аналог COUNT(DISTINCT field) в SQL. Реализован через алгоритм HyperLogLog++, поэтому результат приближённый — погрешность порядка $1$–$5\%$ при настройках по умолчанию:
GET /orders/_search
{
"size": 0,
"aggs": {
"уникальные_клиенты": { "cardinality": { "field": "customer_id" } }
}
}Для повышения точности используйте "precision_threshold" (максимум $40\,000$), но это дороже по памяти. На миллионах уникальных значений приближённый результат за миллисекунды — обычно именно то, что нужно.
Bucket-агрегации: документы по группам
Bucket-агрегации не возвращают одно число — они делят документы на корзины. В каждом бакете есть поле doc_count — количество документов, попавших в него.
#### terms — группировка по значению поля
Самая частая агрегация. Разбивает документы по уникальным значениям поля:
GET /orders/_search
{
"size": 0,
"aggs": {
"по_категории": {
"terms": { "field": "category", "size": 10 }
}
}
}> Важно: поле category должно быть типа keyword (или text с sub-field .keyword). По text-полям terms-агрегация не работает корректно — там хранятся отдельные токены, а не исходные строки. Подробнее — в статье Типы полей: text, keyword, числа и даты.
Пример ответа:
{
"aggregations": {
"по_категории": {
"buckets": [
{ "key": "электроника", "doc_count": 520 },
{ "key": "одежда", "doc_count": 310 },
{ "key": "книги", "doc_count": 180 }
]
}
}
}Параметр "size": 10 задаёт, сколько топ-бакетов вернуть. Это не ограничение на документы — это количество уникальных значений в ответе.
#### range — числовые диапазоны
Разбивает по произвольным числовым границам. Каждый диапазон — полуоткрытый интервал: from включительно, to не включительно:
GET /orders/_search
{
"size": 0,
"aggs": {
"ценовые_диапазоны": {
"range": {
"field": "price",
"ranges": [
{ "key": "бюджетный", "to": 500 },
{ "key": "средний", "from": 500, "to": 2000 },
{ "key": "премиальный", "from": 2000 }
]
}
}
}
}Параметр "key" даёт удобное имя бакету вместо автоматически генерируемого "*-500.0".
#### date_histogram — временные срезы
Группирует по временным интервалам — незаменима для построения графиков динамики:
GET /orders/_search
{
"size": 0,
"aggs": {
"заказы_по_месяцам": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month",
"format": "yyyy-MM"
}
}
}
}calendar_interval принимает: minute, hour, day, week, month, quarter, year. Для фиксированных промежутков — например, каждые $12$ часов — используйте "fixed_interval": "12h".
Пример ответа:
{
"aggregations": {
"заказы_по_месяцам": {
"buckets": [
{ "key_as_string": "2024-01", "doc_count": 341 },
{ "key_as_string": "2024-02", "doc_count": 289 },
{ "key_as_string": "2024-03", "doc_count": 412 }
]
}
}
}Первый шаг к вложенности
Bucket-агрегации становятся по-настоящему мощными, когда внутрь бакетов вкладываются другие агрегации. Например, terms по категориям $+$ avg по цене даёт среднюю цену в каждой категории за один запрос. Это тема статьи Вложенные агрегации и аналитические сценарии.
См. также
- Вложенные агрегации и аналитические сценарии — sub-aggregations и комплексные срезы данных
- Агрегации вместе с поиском и фильтрами — как ограничить выборку перед агрегацией и строить фасеты
- Типы полей: text, keyword, числа и даты — почему
terms-агрегация требуетkeyword, а неtext - Маппинг: явный, динамический и шаблоны индексов — как правильно объявить поля для агрегаций
- Query DSL: структура запроса и контекст query vs filter — контекст фильтрации, который агрегации могут использовать