Pull to refresh
0
SmartProgress
Сервис постановки и достижения целей

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

Reading time 5 min
Views 40K
В статье рассмотрим, как и зачем применять 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


— Достижение целей
Only registered users can participate in poll. Log in, please.
Тема следующей статьи
55.38% ElasticSearch и river. Перебрасываем данных из SQL/NoSQL базы в ES 72
43.85% Warmer — подогреваем и ускоряем ES перед боем 57
0.77% Другое (в комментариях) 1
130 users voted. 35 users abstained.
Tags:
Hubs:
+1
Comments 6
Comments Comments 6

Articles

Information

Website
smartprogress.do
Registered
Founded
Employees
2–10 employees
Location
Россия