ElasticSearch — mapping и поиск без сюрпризов

  • Tutorial
В статье рассмотрим, как и зачем применять mapping. Нужен ли он вообще и в каких случаях. Я приведу примеры его установки, а так же постараюсь поделиться некоторыми полезными хитростями, которые могут помочь вам в усовершенствование поиска на вашем сайте.

Всем, кому интересен современный поисковый движок ElasticSearch, прошу под кат.


В прошлой статье общим голосование была выбрана эта тема. В этой статье я размещу опять голосование, прошу принять участие. Я постараюсь написать максимально полный цикл статей по ES, если это будет интересно публике.

Зачем нужен mapping?


Mapping похож на определение таблицы в sql базах данных. Мы явно указываем тип каждого поля и дополнительные параметры, такие как анализатор, дефолтное значение, source и так далее. Подробнее ниже.

Мы можем указать mapping при создании индекса, тем самым за один запрос определить для всех типов в индексе.
curl -XPOST 'http://localhost:9200/test' -d '{
    "settings" : {
        "number_of_shards" : 1
    },
    "mappings" : {
        "type1" : {
            "_source" : { "enabled" : false },
            "properties" : {
                "field1" : { "type" : "string", "index" : "not_analyzed" }
            }
        }
    }
}'


Так же можем указать mapping напрямую для определённого типа в индексе:
$ curl -XPUT 'http://localhost:9200/twitter/tweet/_mapping' -d '{
    "tweet" : {
        "properties" : {
            "message" : {"type" : "string", "store" : true }
        }
    }
}'


А можем указать mapping сразу для нескольких индексов:
$ curl -XPUT 'http://localhost:9200/kimchy,elasticsearch/tweet/_mapping' -d '{ ... }'


Так ли он нужен?


ES не требует явного определения типов данных в документе. В большинстве простых случаев он определяет тип данных верно.
Так зачем тогда его нужно определять?
Ну во первых, это полезно для чистоты кода и уверенности в том, что в данный момент хранится в индексе.
Важная особенность mapping это тонкая настройка данных и их обработка, т.к. мы можем указать, нужно ли анализировать поле, нужно ли хранить исходник. Давайте посмотрим большинство возможностей на примере.

Базовые типы данных


Думаю, все уже догадались, о чём пойдёт речь. Базовых типов всего 7: string, integer/long, float/double, boolean, null

Пример:
$ curl -XPUT 'http://localhost:9200/twitter/tweet/_mapping' -d '{
    "tweet" : {
        "_source" : {"enabled" : false},
        "properties" : {
            "user" : {"type" : "string", "index" : "not_analyzed"},
            "message" : {"type" : "string", "null_value" : "na", "store": true},
            "postDate" : {"type" : "date"},
            "priority" : {"type" : "integer"},
            "rank" : {"type" : "float", "index_name" : "rating"}
        }
    }
}'


Тут мы указали дополнительных параметры:
  1. "_source" : {"enabled" : false} — Тем самым мы указали, что хранить исходные данные для этого типа не нужно. Когда это может понадобится? Например у вас есть очень тяжелый документ с кучей информации, которую нужно только индексировать, но не нужно выводить в ответе
  2. "store": true для поля message говорит о том, что это исходник поля необходимо сохранять в индексе
  3. "index" : "not_analyzed" — тут мы указали, что это поле не должно анализироваться, т.е. должно хранится как есть. Какие бывают анализаторы
  4. "null_value" : "na" — дефолтное значение для поля
  5. "index_name" : "rating" — тут мы указали алиас для поля. Теперь мы можем обращаться к нему как к «rank» так и к «rating»


Примечание: По умолчанию _source = true и весь документ хранится в индексе в исходном состояние и возвращается по запросу. И это работает быстрее, чем хранить в индексе отдельные поля, при условии, что ваш документ не огромен. Тогда хранение только необходимых полей может дать профит. Поэтому я не рекомендую трогать это поле без веской на то причины.

Типы array/object/nested

Мы можем указать не только тип массив для поля, но и указать тип для каждого поля внутри массива, вот пример:
#source
{
    "tweet" : {
        "message" : "some arrays in this tweet...",
        "lists" : [
            {
                "name" : "prog_list",
                "description" : "programming list"
            },
            {
                "name" : "cool_list",
                "description" : "cool stuff list"
            }
        ]
    }
}
#mapping
{
    "tweet" : {
        "properties" : {
            "message" : {"type" : "string"},
            "lists" : {
                "properties" : {
                    "name" : {"type" : "string"},
                    "description" : {"type" : "string"}
                }
            }
        }
    }
}

Для объектов всё то же самое, за исключение того, что он может быть динамичным (по умолчанию так и есть).
Т.е. вы в любое время можете добавить новое поле в объект и он добавится без ошибок.
Отключить можно так: "dynamic" : false. Подробнее можно почитать тут.

Nested(вложенный) type

По сути, мы определяем документ внутри документа. Зачем это нужно? Отличный пример из документации:
{
    "obj1" : [
        {
            "name" : "blue",
            "count" : 4
        },
        {
            "name" : "green",
            "count" : 6
        }
    ]
}


Если мы будем искать name = blue && count>5 то этот документ будет найден, что бы избежать такого сценария, стоит использовать nested тип.
Пример:
{
    "type1" : {
        "properties" : {
            "obj1" : {
                "type" : "nested",
                "properties": {
                    "name" : {"type": "string", "index": "not_analyzed"},
                    "count" : {"type": "integer"}
                }
            }
        }
    }
}


Указывать properties для элементов объекта не обязательно, ES сделает это автоматически.
Для поиска по nested типу следует использовать nested query или nested filter.

Multi-fields


Начиная с версии 1.0 этот прекрасный параметр был добавлен ко все базовым типам (кроме nested и object).
Что он делает? Этот параметр позволяет указать разные настройки маппинга для одного поля.
Зачем это может быть нужно? например, у вас есть поле, по которому вы хотите и искать и группировать. Если отключить анализатор, поиск будет работать не на полную катушку, а если включить, то группировать мы будем не по сырым данным, а по обработанным. Например, Санкт-Петербург после анализатора будет «Санкт» и «Петербург» (возможно слегка по-другому, но для примера сойдёт). Если мы будет группировать по этому полю, то получим не то, что хотели.

Пример:
"title": {
    "type": "string",
    "fields": {
        "raw":   { "type": "string", "index": "not_analyzed" }
    }
}

Теперь мы можем обращаться к «title» за поиском и к «raw» за группировкой и любыми другими видами сортировки.

Остальные типы

ES поддерживает еще 4 типа данных:
  1. ip type — хранение ip в виде цифр
  2. geo point type — хранение координат (удобно при поиске ближайших объектов к определённой координате)
  3. geo point type — довольно специфичный тип для хранение определённых полигонов
  4. attachment type — Хранение файлов в базе закодированных в base64. Обычно используется с связке с собственным анализатором. (Хотя как по мне, удовольствие сомнительное)

Я не стал рассматривать эти типы подробно, т.к. они довольно специфичны или ничем кардинально не отличаются от выше рассмотренных (например IP).

Надеюсь, что я смог доходчиво рассказать о главных функциях mapping'a в ES. Если у вас есть вопросы, рад буду ответить.

Другие статьи по ES:
ElasticSearch — агрегация данных
ElasticSearch и поиск наоборот. Percolate API


— Достижение целей

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Тема следующей статьи

  • 56,6%ElasticSearch и river. Перебрасываем данных из SQL/NoSQL базы в ES69
  • 42,6%Warmer — подогреваем и ускоряем ES перед боем52
  • 0,8%Другое (в комментариях)1
  • +1
  • 27,2k
  • 6
SmartProgress
0,00
Сервис постановки и достижения целей
Поделиться публикацией

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 6

    0
    Можно немного подробнее про _source и store? Я не очень понял, в чём между ними разница. Насколько я помню, даже если мы храним весь _source, то можем сказать, что «вытащи мне только определённые поля из _source». Как я понял, store делает тоже самое. В чём разница между ними? Когда что лучше использовать?

    И ещё немного не по теме вопрос, насколько «умный» анализатор в ES для русского и английских языков? Другими словами в какую нормальную форму он приводит слова? (Если сравнивать со Sphinx, где есть стеммер, который просто обрезает окончания, и лемматайзер, который при нормализации приводит слово в правильную нормальную форму.)
      0
      _source вытаскивает весь документ, а не по полям, если документ очень тяжелый, а выводить нужно парочку полей, то есть смысл хранить только эти поля. используя «store».
      Анализаторов в ES довольно много, для русского языка лучше использовать сторонний, например этот, зато английский на порядок лучше Sphinx.
      Подробнее о языковых анализаторах можно почитать тут
        0
        Прослоупочу, но всё же.
        В запросе можно указать "_source":[«id»,«title»], и в выдаче будут только эти поля ;)
      0
      зато английский на порядок лучше Sphinx

      У кого лучше я так и не понял из вашей фразы, у сфинкс или эластик?
        0
        Простите мой французский. У ES стандартный анализатор лучше, чем у Sphinx
          0
          А, ну да. Впрочем russian_morphology от imotov как мне кажется — тоже получше, чем у Sphinx

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое