Оглавление курса

Метрики и группировки

Построим дерево агрегаций и определим, какие документы участвуют в каждом расчёте.

Разделов: 9Elasticsearch 9.4.3Ещё не прочитан

Чему вы научитесь

  • Область расчёта
  • Метрики и интервалы
  • Имена и вложенность

Построим дерево агрегаций и определим, какие документы участвуют в каждом расчёте.

Примеры course-products используют учебную схему и товар из главы 5 «Mapping и типы полей». Разборы упражнений ниже используют отдельный публичный набор тренажёра.

Логика главы
  • Область расчёта
  • Метрики и интервалы
  • Имена и вложенность

Построим дерево агрегаций и определим, какие документы участвуют в каждом расчёте.

Курс ориентирован на Elasticsearch 9.4.3. Разборы упражнений используют публичный набор products; остальные запросы показывают отдельные учебные индексы и могут требовать подготовки или прав, недоступных в тренажёре. Ответы сокращены, score может меняться. Для расширенных функций проверяйте лицензию и вариант развёртывания.

01 / 09

Область расчёта

Агрегации выполняются над документами, прошедшими query, независимо от числа возвращённых hits. size=0 отключает выдачу документов, но не расчёты. Bucket aggregation разбивает множество на группы, metric aggregation вычисляет показатель. Подагрегация получает документы родительского bucket, поэтому средняя цена внутри бренда отличается от средней по всему каталогу.

Самопроверка
02 / 09

Метрики и интервалы

sum, avg, min, max и stats применяют к числовым значениям; пропуски и многозначность требуют понимания. Range задаёт явные границы, histogram — шаг по числам. date_histogram различает calendar_interval и fixed_interval: календарный день при переходе времени не всегда равен 24 часам. Часовой пояс — часть аналитического определения, а не косметическая настройка подписи.

Самопроверка
03 / 09

Имена и вложенность

Имена агрегаций являются ключами контракта ответа. Сортировка hits по price не сортирует buckets по средней цене — это разные уровни. terms по brand формирует группы, а avg внутри каждой рассчитывает их метрику. Название nested aggregation означает конкретную работу с nested-документами; обычная вложенность агрегаций сама по себе nested mapping не требует.

Средняя цена считается отдельно в каждой выбранной группе бренда.

POST /course-products/_search
{
  "size": 0,
  "aggs": {
    "brands": {
      "terms": {
        "field": "brand",
        "size": 10
      },
      "aggs": {
        "average_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

Как читать результат

Средняя цена считается отдельно в каждой выбранной группе бренда.

Самопроверка
04 / 09

Разбор на примере каталога

Для трёх товаров Sony/100, Sony/200 и Bose/50 terms по brand создаёт две группы. avg(price) внутри Sony даёт 150, внутри Bose — 50. Если query заранее оставил только price<150, среднее Sony становится 100: область query применяется до группировки. При этом size=1 изменит число hits, но не эти средние. Сначала определяйте множество документов, затем группировку, затем метрику — это простой способ читать большие аналитические деревья.

Самопроверка
05 / 09

Количество по брендам

terms-агрегация создаёт buckets для точных индексированных значений. Здесь группировка идёт по category вместо бренда. У каждого bucket есть key и doc_count. size внутри terms управляет числом buckets, а корневой size — числом документов. Это разные параметры с одинаковым именем.

Для групп по целому значению нужны keyword-поля: category или brand.keyword. По умолчанию terms предпочитает группы с большим счётчиком, разрешая равенство через ключ. На промышленном индексе с несколькими шардами возможны особенности точности top terms; здесь один primary shard, а size примеров достаточен для всех значений.

Разберём другой сценарий

POST /products/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "category",
        "size": 10
      }
    }
  }
}
Ответ Elasticsearch
Сокращённый ответ · JSON
{
  "hits": {
    "total": {
      "value": 300,
      "relation": "eq"
    },
    "hits": []
  },
  "aggregations": {
    "categories": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "Accessories",
          "doc_count": 75
        },
        {
          "key": "Audio",
          "doc_count": 75
        },
        {
          "key": "Computers",
          "doc_count": 75
        },
        {
          "key": "Smartphones",
          "doc_count": 75
        }
      ]
    }
  }
}

Как читать результат

Документы не возвращаются. categories.buckets содержит ключи и счётчики набора, а hits.total по-прежнему показывает общее число совпавших документов.

Закрепить в задании 21
06 / 09

Средняя цена

avg вычисляет среднее индексированных числовых значений текущей области. Пример считает среднюю цену только для Audio благодаря верхнеуровневому query. В ответе метрики находится value, а не список товаров или счётчик группы. Без query показатель относился бы ко всему индексу.

Документы без поля метрики не добавляют значение в обычное среднее. Если числовых значений нет, value равен null, а не нулю. Десятичные результаты — числовые вычисления: несущественные различия floating-point не меняют смысл.

Разберём другой сценарий

POST /products/_search
{
  "size": 0,
  "query": {
    "term": {
      "category": "Audio"
    }
  },
  "aggs": {
    "audio_average": {
      "avg": {
        "field": "price"
      }
    }
  }
}
Ответ Elasticsearch
Сокращённый ответ · JSON
{
  "hits": {
    "total": {
      "value": 75,
      "relation": "eq"
    },
    "hits": []
  },
  "aggregations": {
    "audio_average": {
      "value": 859.4133333333333
    }
  }
}

Как читать результат

audio_average.value обобщает все совпавшие товары Audio, а не первую страницу. Имя метрики выбрано для этого примера.

Закрепить в задании 22
07 / 09

Границы цен

min и max — независимые метрические агрегации, поэтому располагаются соседями в aggs. Пример ограничивает набор Accessories и запрашивает обе границы цены. Метрика наследует текущую область: сосед не меняет набор документов другой метрики.

Это наблюдаемые значения, а не границы range-фильтра. Если query допускает цену ниже 800, максимальная фактическая цена может быть меньше 800. На пустом наборе метрики равны null: не заменяйте отсутствие значения выдуманным нулём.

Разберём другой сценарий

POST /products/_search
{
  "size": 0,
  "query": {
    "term": {
      "category": "Accessories"
    }
  },
  "aggs": {
    "lowest": {
      "min": {
        "field": "price"
      }
    },
    "highest": {
      "max": {
        "field": "price"
      }
    }
  }
}
Ответ Elasticsearch
Сокращённый ответ · JSON
{
  "hits": {
    "total": {
      "value": 75,
      "relation": "eq"
    },
    "hits": []
  },
  "aggregations": {
    "lowest": {
      "value": 99
    },
    "highest": {
      "value": 2499
    }
  }
}

Как читать результат

Оба value описывают один набор Accessories. Их можно получить без запроса и сортировки отдельных документов.

Закрепить в задании 23
08 / 09

Метрики по брендам

Bucket может содержать дочерние aggs. Сгруппируйте категории и вычислите average_price для каждой. Получатся отдельные средние, а не один глобальный показатель рядом со всеми группами. Ответ повторяет дерево запроса: читайте его от родительского ключа к потомкам.

Вложенность агрегаций не равна nested query по полю с mapping nested. Товары содержат обычные скалярные поля: для дерева категория → метрика nested-mapping не нужен. Соблюдайте лимиты и оценивайте умножение числа buckets перед добавлением новой группировки.

Разберём другой сценарий

POST /products/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "category",
        "size": 10
      },
      "aggs": {
        "average_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}
Ответ Elasticsearch
Сокращённый ответ · JSON
{
  "hits": {
    "total": {
      "value": 300,
      "relation": "eq"
    },
    "hits": []
  },
  "aggregations": {
    "categories": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "Accessories",
          "doc_count": 75,
          "average_price": {
            "value": 859.4
          }
        },
        {
          "key": "Audio",
          "doc_count": 75,
          "average_price": {
            "value": 859.4133333333333
          }
        },
        {
          "key": "Computers",
          "doc_count": 75,
          "average_price": {
            "value": 589.2
          }
        },
        {
          "key": "Smartphones",
          "doc_count": 75,
          "average_price": {
            "value": 589.88
          }
        }
      ]
    }
  }
}

Как читать результат

Каждый bucket категории содержит собственный average_price.value. Положение метрики внутри bucket указывает, какой набор она описывает.

Закрепить в задании 25
09 / 09

Аналитика категорий и брендов

Используйте три уровня: buckets категорий, затем брендов, затем листья числовых метрик. Пример анализирует active, группирует по category и brand.keyword и вычисляет три показателя цены для каждой пары. Метрики внутри бренда наследуют и категорию, и бренд.

Query расположен вне aggs и ограничивает все группы. Каждый terms.size локален для своего уровня. При размерах 10 и 20 консервативная оценка числа buckets равна 10 + 10 × 20 = 210, что укладывается в бюджет 1000. Метрики занимают третий уровень, но не умножают число buckets.

Читайте ответ как categories.buckets → brands.buckets → metric.value. Не смешивайте метрики разных пар категория–бренд без отдельного требования. Среднее средних групп в общем случае не равно среднему всех документов.

Три уровня агрегаций
  • query: active
    • categories (уровень 1)
      • brands (уровень 2)
        • avg / min / max (уровень 3)

Каждый уровень вложенности уточняет набор документов до вычисления метрик.

Разберём другой сценарий

POST /products/_search
{
  "size": 0,
  "query": {
    "term": {
      "status": "active"
    }
  },
  "aggs": {
    "categories": {
      "terms": {
        "field": "category",
        "size": 10
      },
      "aggs": {
        "brands": {
          "terms": {
            "field": "brand.keyword",
            "size": 20
          },
          "aggs": {
            "average_price": {
              "avg": {
                "field": "price"
              }
            },
            "min_price": {
              "min": {
                "field": "price"
              }
            },
            "max_price": {
              "max": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}
Ответ Elasticsearch
Сокращённый ответ · JSON
{
  "hits": {
    "total": {
      "value": 100,
      "relation": "eq"
    },
    "hits": []
  },
  "aggregations": {
    "categories": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "Accessories",
          "doc_count": 25,
          "brands": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "apple",
                "doc_count": 25,
                "average_price": {
                  "value": 859.4
                },
                "min_price": {
                  "value": 99
                },
                "max_price": {
                  "value": 2499
                }
              }
            ]
          }
        },
        {
          "key": "Audio",
          "doc_count": 25,
          "brands": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "apple",
                "doc_count": 25,
                "average_price": {
                  "value": 859.4
                },
                "min_price": {
                  "value": 99
                },
                "max_price": {
                  "value": 2499
                }
              }
            ]
          }
        },
        {
          "key": "Computers",
          "doc_count": 25,
          "brands": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "Sony",
                "doc_count": 25,
                "average_price": {
                  "value": 589.2
                },
                "min_price": {
                  "value": 49
                },
                "max_price": {
                  "value": 1299
                }
              }
            ]
          }
        },
        {
          "key": "Smartphones",
          "doc_count": 25,
          "brands": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "Sony",
                "doc_count": 25,
                "average_price": {
                  "value": 589.2
                },
                "min_price": {
                  "value": 49
                },
                "max_price": {
                  "value": 1299
                }
              }
            ]
          }
        }
      ]
    }
  }
}

Как читать результат

Каждый бренд находится внутри одной категории. Его метрики описывают только active в этой паре, даже если тот же бренд встречается в другом месте ответа.

Закрепить в задании 29

Самопроверка

Сначала ответьте самостоятельно, затем раскройте объяснение.

1. Влияет ли size=0 на агрегации?

Нет, отключается возврат hits.

2. Какие документы видит подагрегация?

Документы родительского bucket.

3. Сортирует ли sort buckets?

Нет, порядок buckets задаётся внутри агрегации.

4. Календарный день всегда 24 часа?

Нет, возможны переходы времени в выбранной зоне.

5. Вложенная агрегация требует nested mapping?

Нет, только специальная nested aggregation работает с такой областью.

Краткая памятка

  • Нет, отключается возврат hits.
  • Документы родительского bucket.
  • Нет, порядок buckets задаётся внутри агрегации.
  • Нет, возможны переходы времени в выбранной зоне.
  • Нет, только специальная nested aggregation работает с такой областью.

Официальная документация

Готовы применить знания?

Подтвердите прочтение, чтобы сохранить своё место. Чтение и решение заданий учитываются отдельно в этом браузере с помощью гостевой cookie.