ElasticSearch - mapping і пошук без сюрпризів

    У статті розглянемо, як і навіщо застосовувати 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"}
        }
    }
}'

 
Тут ми вказали додаткових параметри:
 
     
"_source" : {"enabled" : false}
— Тим самим ми вказали, що зберігати вихідні дані для цього типу не потрібно. Коли це може знадобиться? Наприклад у вас є дуже важкий документ з купою інформації, яку потрібно тільки індексувати, але не потрібно виводити у відповіді
 
"store": true
для поля message говорить про те, що це ісходник поля необхідно зберігати в індексі
 
"index" : "not_analyzed"
— тут ми вказали, що це поле не повинно аналізуватися, тобто повинно зберігається як є. Які бувають аналізатори
 
"null_value" : "na"
— дефолтний значення для поля
 
"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 типи даних:
 
     
ip type — зберігання ip у вигляді цифр
 geo point type — зберігання координат (зручно при пошуку найближчих об'єктів до певної координаті)
 geo point type — досить специфічний тип для зберігання певних полігонів
 attachment type — Зберігання файлів в базі закодованих в base64. Зазвичай використовується з зв'язці з власним аналізатором. (Хоча як на мене, задоволення сумнівне)
 
Я не став розглядати ці типи докладно, тому що вони досить специфічні або нічим кардинально не відрізняються від вище розглянутих (наприклад IP).
 
Сподіваюся, що я зміг дохідливо розповісти про головні функції mapping'a в ES. Якщо у вас є питання, радий буду відповісти.
 
Інші статті по ES:
 ElasticSearch — агрегація даних
 ElasticSearch і пошук навпаки. Percolate API
 
 
  

    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.