Как читать ответ _search

Разбираем JSON-ответ, который Elasticsearch возвращает на запрос _search, поле за полем.

Ответ _search — это JSON, где найденные документы лежат в hits.hits, отсортированные по релевантности _score.

Зачем разбираться в ответе

Чтобы строить поиск, нужно уметь читать, что вернул ES: сколько всего нашлось, какие документы попали в выдачу, насколько они релевантны и почему. Без этого вы не сможете ни отладить запрос, ни показать результаты пользователю.

Структура ответа

{
  "took": 5,
  "timed_out": false,
  "hits": {
    "total": { "value": 42, "relation": "eq" },
    "max_score": 3.21,
    "hits": [
      {
        "_index": "products",
        "_id": "1",
        "_score": 3.21,
        "_source": { "title": "Кофемашина", "price": 24990 }
      }
    ]
  }
}

Поля верхнего уровня

ПолеСмысл
tookсколько миллисекунд занял запрос
timed_outуложился ли в таймаут
hits.total.valueсколько всего документов подошло
hits.max_scoreмаксимальная релевантность в выдаче
hits.hitsмассив самих найденных документов

Внутри каждого hit

  • _index, _id — где лежит документ и его идентификатор;
  • _score — оценка релевантности; чем больше, тем выше документ в выдаче;
  • _source — исходный JSON документа (то, что вы проиндексировали).

total.relation — важная тонкость

Поле total содержит не только value, но и relation. По умолчанию ES считает точное число попаданий лишь до 10000, дальше ставит relation: "gte" («не меньше чем»). То есть {"value": 10000, "relation": "gte"} значит «нашлось 10000 или больше». Это сделано ради скорости — точный подсчёт миллионов попаданий дорог. Если нужна точная цифра, есть параметр track_total_hits: true.

Как работает под капотом

Координирующий узел собирает топ результатов с каждого шарда, объединяет их, пересортировывает по _score и берёт нужное окно (from/size). По умолчанию возвращаются первые 10 документов. _score вычисляется по формуле релевантности (BM25) — об этом следующий раздел. Поэтому один и тот же набор документов на разные запросы получает разный _score: оценка зависит от запроса, а не от документа самого по себе.

Частые ошибки

  • Принимать total.value за точное число. Без track_total_hits оно может быть «10000+», смотрите на relation.
  • Искать данные документа не там. Содержимое документа — в hits.hits[i]._source, а не в корне ответа.
  • Ждать все результаты сразу. Возвращаются только первые size (по умолчанию 10); остальное — через пагинацию.

Итоги

  • Найденные документы — в hits.hits, их содержимое — в _source, релевантность — в _score.
  • total.value с relation: "gte" означает «не меньше», точное число — через track_total_hits.
  • По умолчанию возвращаются 10 самых релевантных документов.
Проверьте себя
1. Где в ответе _search лежит исходное содержимое найденного документа?
AВ корне ответа
BВ hits.hits[i]._source
CВ поле took
DВ hits.max_score
2. Что означает hits.total с relation: "gte" и value: 10000?
AРовно 10000 документов
BНе меньше 10000 документов (точный подсчёт остановлен ради скорости)
CОшибка запроса
DРовно 10000 миллисекунд