Что такое поля типа `nested` в Elasticsearch и какую проблему они решают?

Ответ

Поля типа nested в Elasticsearch позволяют индексировать массивы объектов таким образом, чтобы сохранить внутреннюю связь между полями каждого объекта в массиве.

Проблема, которую решает nested

По умолчанию Elasticsearch "уплощает" массивы объектов. Рассмотрим документ с отзывами:

{
  "reviews": [
    { "author": "John", "rating": 5 },
    { "author": "Alice", "rating": 1 }
  ]
}

При стандартном индексировании (тип object) данные сохранятся так:

"reviews.author": ["John", "Alice"],
"reviews.rating": [5, 1]

Связь между автором и его оценкой теряется. Из-за этого запрос "найти документы, где автор John и рейтинг 1" ошибочно найдет этот документ, так как оба значения присутствуют в соответствующих массивах, хоть и в разных объектах.

Как nested решает эту проблему

Тип nested заставляет Elasticsearch индексировать каждый объект в массиве как отдельный, скрытый внутренний документ. Это сохраняет связи.

1. Определение маппинга:

PUT /products
{
  "mappings": {
    "properties": {
      "reviews": {
        "type": "nested" 
      }
    }
  }
}

2. Поиск по nested полю:

Для запросов к таким полям используется специальный nested query. Он ищет совпадения в рамках одного внутреннего документа.

GET /products/_search
{
  "query": {
    "nested": {
      "path": "reviews",
      "query": {
        "bool": {
          "must": [
            { "match": { "reviews.author": "John" }},
            { "range": { "reviews.rating": { "gte": 4 }}}
          ]
        }
      }
    }
  }
}

Этот запрос найдет только те продукты, где есть отзыв от John с рейтингом 4 или выше. Запрос на author: "John" и rating: 1 уже не найдет документ из примера выше, что является корректным поведением.

Важно: Использование nested полей увеличивает размер индекса и немного замедляет запросы, поэтому их следует применять только тогда, когда сохранение связей внутри объектов массива действительно необходимо.

Ответ 18+ 🔞

О, слушай, а вот есть в Elasticsearch такая штука, которая с первого взгляда кажется простой, а потом бац — и ты уже сидишь и думаешь: «Ёпта, а почему мой запрос находит какую-то дичь?». Это всё про поля типа nested, сука.

Представь себе, у тебя есть документ с отзывами, ну, обычное дело:

{
  "reviews": [
    { "author": "John", "rating": 5 },
    { "author": "Alice", "rating": 1 }
  ]
}

И вроде бы всё логично: Джон — молодец, поставил пятёрку, а Алиса — хитрая жопа, вкатила единицу. Но Elasticsearch по умолчанию, этот, блядь, умник, поступает как полный распиздяй: он берёт и «уплощает» этот массив. В итоге внутри индекса получается каша:

"reviews.author": ["John", "Alice"],
"reviews.rating": [5, 1]

Связь между полями — на хуй! И вот ты такой пишешь запрос: «Найди мне, где автор John и рейтинг 1». А эта мудя тебе радостно подсовывает документ выше! Потому что John в массиве есть, и 1 в массиве есть. А то, что это разные объекты — да похуй, блядь. Вот это пиздец, да? Запрос врёт, как сивый мерин.

Так вот, чтобы не было такого пиздеца, и придумали nested. Это как сказать Elasticsearch: «Э, сабака сука, не размазывай мне всё в одну кучу, индексируй каждый объект в массиве отдельно, как скрытый документ!».

1. Сначала настраиваешь маппинг, чтоб всё было чётко:

PUT /products
{
  "mappings": {
    "properties": {
      "reviews": {
        "type": "nested" 
      }
    }
  }
}

Всё, теперь reviews — не просто объект, а nested. Связи внутри каждого отзыва сохранятся.

2. А чтобы искать по этому полю, нужно использовать специальный nested query:

GET /products/_search
{
  "query": {
    "nested": {
      "path": "reviews",
      "query": {
        "bool": {
          "must": [
            { "match": { "reviews.author": "John" }},
            { "range": { "reviews.rating": { "gte": 4 }}}
          ]
        }
      }
    }
  }
}

Смотри, какой красивый запрос. Он ищет ВНУТРИ одного вложенного документа. То есть найдёт только те продукты, где есть отзыв именно от John И именно с рейтингом 4 или выше. А наш предыдущий пример, где John (5) и Alice (1) — уже не подойдёт, потому что это два разных вложенных документа. Ура, блядь, логика восторжествовала!

Но, конечно, за всё хорошее надо платить. Nested поля жрут больше памяти и слегка тормозят запросы. Так что юзай их с умом, только когда реально нужно сохранить связь между полями внутри объекта. А то накрутишь этих nested на овердохуища — и потом будешь чесать репу, почему индекс раздулся как шарик, а поиск еле ползёт. В общем, думай головой, а не жопой.