КатегорииElasticsearch

Elasticsearch — Урок 3.1 Mapping: схема документов

Маппинг (сопоставление) — это процесс определения схемы или структуры документов. Он описывает свойства полей в документе. Свойства поля включают тип данных (например, string, integer и т.д.) и метаданные.

Подобно тому, как вы определяете схему таблицы в SQL, важно рассказать про схему документов, прежде чем индексировать любые данные. Как мы обсуждали ранее, тип в Elasticsearch похож на таблицу SQL, который группирует документы аналогичного характера (например тип для пользователей, тип для заказов). Каждый тип имеет схему. Наличие разных схем данных также может быть мотивацией для определения нового типа.

Apache Lucene, которая хранит ваши данные под капотом, не имеет понятия типов. Информация о типе хранится в метаданных и обрабатывается Elasticsearch.

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

{
   "id": 1,
   "name": "User1",
   "age": "55",
   "gender": "M",
   "email": "[email protected]",
   "last_modified_date": "2017-01-03"
 }

 {
   "id": 2,
   "name": "User2",
   "age": "40",
   "gender": "F",
   "email": "[email protected]",
   "last_modified_date": "2016-12-02"
 }

Затем мы хотим запросить всех пользователей старше 50. Если мы проиндексируем эти документы как есть, мы не сможем запускать запрос, так как age по умолчанию будет индексироваться как текст, и вы не можете выполнять запросы на основе диапазонов над текстовыми полями. Аналогично, для date вы должны сообщить Elasticsearch, как форматируется дата. Например, для даты 2017-01-03Elasticearch не сможет сказать, является что является месяцем 01или 03. Мы должны указать формат. Формат даты может быть указан при определении схемы.

Динамическое отображение

Когда вы индексируете документ без указания сопоставления, Elasticsearch автоматически определяет типы данных для полей в документе. Чтобы понять, как работает динамическое сопоставление, давайте попробуем проиндексировать документ человека, как показано ниже:

 PUT example3/person/1
 {
   "name": "john",
   "age": 100,
   "date_of_birth": "1970/01/01"
 }

Теперь давайте проверим сопоставление типа человека, которое устанавливается автоматически:

GET example3/person/_mapping

А вот и ответ:

{
   "example3": {
     "mappings": {
       "person": {
         "properties": {
           "age": {
             "type": "integer"
           },
           "date_of_birth": {
             "type": "date",
             "format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
           },
           "name": {
             "type": "keyword"
           }
         }
       }
     }
   }
 }

Вы можете видеть из предыдущего ответа, что Elasticsearch определил тип данных age как числовое, date_of_birthкак дату и nameкак строковое. Когда встречается новое поле, он пытается определить, является ли поле логическим, числовым, текстовым или датой.

Сопоставление числовых и логических полей данных является простым, но для сопоставления полей даты сначала проверяется значение строки, чтобы увидеть, соответствует ли оно любым известным шаблонам дат.

По умолчанию строковое значение проверяется в отношении трех форматов дат, показанных ниже:

"strict_date_optional_time", "yyyy/MM/dd HH:mm:ss Z", "yyyy/MM/dd Z"

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

PUT example3
{
 "mappings": {
    "news": {
      "dynamic" : false,
      "properties": {
         "desc": {
           "type": "text"
         }
       }
     }
   }
}

Динамическая настройка принимает три разных значения:

  • true: по умолчанию новые поля автоматически добавляются к схеме
  • false : поля не в схеме игнорируются
  • strict: будет вызвано исключение, если вы пытаетесь проиндексировать документ с неизвестным полем

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

 PUT example3/_mapping/news
 {
   "date_detection": false,
   "properties": {
     "source": {
       "type": "text"
     }
   }
 }

Создать индекс с отображением

Чтобы определить или добавить схему, мы должны использовать API маппинга.

API маппинга позволит вам сделать следующее:

  • Создать новый индекс со схемой данных
  • Добавить новый тип в существующий индекс
  • Добавить новое поле в существующий тип

Сначала давайте создадим индекс с именем example3 и определим схему для типа user.

Обратите внимание, что метод HTTP, используемый для запроса — PUT.

PUT example3
 {
   "mappings": {
    "user": {
       "properties": {
         "age": {
           "type": "integer"
         },
         "email": {
           "type": "keyword"
         },
         "gender": {
           "type": "keyword"
         },
         "id": {
           "type": "integer"
         },
         "last_modified_date": {
           "type": "date",
           "format": "yyyy-MM-dd"
         },
         "name": {
           "type": "keyword"
         }
       }
     }
   }
 }

Если все прошло успешно, вы должны увидеть ответ, как показано ниже:

{
  "acknowledged": true 
}

Поскольку схема задана, при индексировании документа поле возраста будет проиндексировано как целое число, и вы можете выполнять запросы диапазона. В предыдущем схеме также обратите внимание, как мы определили формат даты yyyy-MM-dd, это поможет понять Elasticsearch, как анализировать это поле.

Добавление нового типа / поля

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

Обратите внимание на _mappingв конце URL-адреса. Вы можете добавить имя типа historyв example3 индекс, как показано ниже:

 PUT example3/_mapping/history
 {
   "properties": {
     "username": {
       "type": "keyword"
     },
     "login_date": {
       "type": "date",
       "format": "yyyy-MM-dd"
     }
   }
 }

Если тип успешно добавлен, вы должны увидеть тоже сообщение что и в прошлый раз. Затем давайте попробуем добавить новое поле к типу, history. Наряду с usernameи login_dateмы также хотим записать местоположение или IP-адрес, с которого вошел пользователь. Чтобы хранить IP-адреса, Elasticsearch имеет специальный тип данных для хранения IP. Мы можем добавить ip_address поле к типу history, как показано ниже:

 PUT example3/_mapping/history
 {
   "properties": {
     "ip_address": {
       "type": "ip"
     }
   }
 }

Установив ip_addressполе как тип ip, мы можем выполнить запросы диапазона и агрегации по IP-адресу. Мы обсудим тип данных ip в скором будущем.

Получение существующей схемы

API маппинга также используется для извлечения существующей схемы. Вы можете проверить схему существующего индекса или типа, как показано ниже:

GET example3/_mapping

Обратите внимание, что метод HTTP, используемый для запроса — GET.

Следующий ответ содержит отображение всех типов в example3:

{
   "example3": {
     "mappings": {
       "history": {
         "properties": {
           "id_address": {
             "type": "ip"
           },
           "login_date": {
             "type": "date",
             "format": "yyyy-MM-dd"
           },
           "username": {
             "type": "keyword"
           }
         }
       },
       "user": {
         "properties": {
           "age": {
             "type": "integer"
           },
           "email": {
             "type": "keyword"
           },
           "gender": {
             "type": "keyword"
           },
           "id": {
             "type": "integer"
           },
           "last_modified_date": {
             "type": "date",
             "format": "yyyy-MM-dd"
           },
           "name": {
             "type": "keyword"
           }
         }
       }
     }
   }
 }

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

GET example3/user/_mapping

Изменение существующей схемы

Схема существующих полей не может быть изменена. Когда документы индексируются, они сохраняются в инвертированном индексе в соответствии с типом данных. Если вы попытаетесь обновить тип данных существующего поля, вы получите исключение, как показано ниже:

{
 "error": { 
     "root_cause": [{
        "type": "merge_mapping_exception", 
        "reason": "Merge failed with failures {[mapper [username] of different type, current_type [string], merged_type [integer]]}"
      }], 
      "type": "merge_mapping_exception",
      "reason": "Merge failed with failures {[mapper [username] of different type, current_type [string], merged_type [integer]]}"
  },
  "status": 400 
}

Вы всегда можете добавлять новые поля или использовать несколько полей для индексации одного и того же поля с использованием нескольких типов данных, но вы не можете обновлять существующее сопоставление. Если вы хотите изменить отображение, вам нужно заново создать индекс или использовать Reindex API. Мы обсудим переиндексацию в 5 уроке. Если вам не нужны уже индексированные данные, вы можете добавить новое поле с правильным типом данных.

Тип данных

В традиционном мире SQL тип данных может быть только простым, таким как integer, boolean и т. д. Поскольку данные в Elasticsearch представлены как JSON, он поддерживает типы данных, которые являются более сложными объекты, массивы и т. д.

Поддерживаются разные типы данных:

Основные типы данных:

  • Text
  • Keyword
  • Date
  • Numeric
  • Boolean
  • Binary

Сложные типы данных:

  • Array
  • Object (объект JSON)
  • Nested (вложенные)

Типы данных Geo:

  • Geo-point (точка)
  • Geo-shape (область)

Специализированные типы данных:

  • ip — тип для хранения ip адреса

Прежде чем мы перейдем к каждому типу данных, давайте поговорим о мета-полях, которые содержатся в каждом документе.

Мета-поля

Каждый индекс, который мы индексируем, имеет следующие мета-поля, также известные как мета-поля идентификации, поскольку они используются для однозначного определения документа:

  • _index: Это имя индекса, к которому принадлежит документ.
  • _uid: Это комбинация _typeи_id.
  • _type: Это тип схемы, к которому принадлежит документ.
  • _id: Это уникальный идентификатор документа.

Как обрабатывать нулевые значения

Когда Elasticsearch встречает нулевое значение JSON в документе, оно пропускает это поле, поскольку оно не может быть проиндексировано. Но если вы хотите искать все документы, содержащие нулевое значение, вы можете сказать Elasticsearch заменить значение null значением по умолчанию.

Например, у нас есть документ с полями usernamedateи login_status, как показано ниже:

{
  "username" : "user1",
  "login_date" : "2017-01-31T00:00:00",
  "login_status" : "successful"
}

И иногда login_statusпо умолчанию посылается как null и в таком случае оно пропускается. Но все же мы хотим сохранить пустое значение заменив  его например UNKNOWN. Мы можем это сделать, указав нулевое значение по умолчанию для login_status, как показано ниже:

PUT example3/_mapping/history
 {
   "properties": {
     "username": {
       "type": "keyword"
     },
     "login_date": {
       "type": "date",
       "format": "yyyy-MM-dd"
     },
     "login_status": {
       "type": "keyword",
       "null_value": "UNKNOWN"
     }
   }
 }

Теперь мы можем запросить все документы history с пустым полем login_status, как показано ниже:

GET example3/history/_search?q=login_status:UNKNOWN

Обратите внимание, что он null_value должен иметь тот же тип данных, что и поле. Целочисленное поле не может иметь нулевое значение, которое является строкой.

Хранение оригинального документа

В Введение в Elasticsearch , мы узнали, как данные хранятся внутри в инвертированном индексе. Наряду с инвертированным индексом исходный документ JSON также сохраняется как поле _source. Поле  _sourceиспользуется , чтобы показать исходный JSON документ в ответе. По умолчанию исходник документа сохраняется. Поле _sourceиспользуется не только для возврата исходного документа, но также для  переиндексации. Вы можете отключить его для сохранения дискового пространства как показано ниже:

PUT example3/_mapping/order
 {
   "_source": {
     "enabled": false
   },
   "properties": {
     "buyer": {
       "type": "keyword"
     },
     "seller": {
       "type": "keyword"
     },
     "itemtitle": {
       "type": "text"
     }
   }
 }

Вы можете отключить поле _source, если требуется только идентификатор документа в ответе и не планируете обновлять или переиндексировать данные.

Поиск по всем полем в документе

Elasticsearch позволяет искать значение по всем полям документа. Для этого вы можете использовать поле _all. Чтобы сделать это возможным, при индексировании документа все значения полей в документе объединены в одну большую строку, разделенную пробелом и индексированную как поле _all. Например, мы проиндексировали документы заказов с полями buyersellerи item_title. Ниже показан пример документа заказа:

PUT example3/order/1
 {
   "buyer": "john",
   "seller": "jake",
   "item_title": "iphone",
   "order_date": "2017-02-08"
 }

Когда предыдущий документ индексируется вместе с полями документа, Elasticsearch индексирует все значения полей в одну большую строку ( "john jake iphone 2017-02-08") в качестве _allполя. Запрос для всех заказов, которые содержат, item_titleкак iphoneпоказано ниже:

GET example3/order/_search?q=item_title:iphone

В предыдущем запросе, мы запросили все документы , которые содержат в item_title значение iphone. Но если мы хотим искать по всем полям мы можем воспользоваться полем _all. По умолчанию, если поле не указано, Elasticsearch запрашивает поле _all, как показано ниже:

GET example3/order/_search?q=iphone

При индексировании поля _all все поля документа объединяются в одну большую строку независимо от типа данных. В предыдущем документе, несмотря на то что полеorder_date имеет тип даты, оно будет обработано как строка, для поля _all.

По умолчанию поле _all включено. Если вы не планируете использовать его можно отключить, как показано ниже. Отключение его приведет к уменьшению размера индекса на диске:

PUT example3/_mapping/order
 {
   "_all": {
     "enabled": false
   },
   "properties": {
     "buyer": {
       "type": "keyword"
     },
     "seller": {
       "type": "keyword"
     },
     "item_title": {
       "type": "text"
     }
   }
 }

Вы не можете отключить поле _all для существующего типа. Чтобы отключить поле _all, вам нужно пересоздать индекс и установить схему. Elasticsearch также поддерживает исключение отдельных полей из поля _all вместо того, чтобы отключать его для всего типа. Вы можете исключить отдельные поля «на лету». Например, если мы хотим, чтобы поля buyer и seller были исключены из _all, мы можем установить include_in_allфлаг false, как показано ниже:

PUT example3/_mapping/order
 {
   "properties": {
     "buyer": {
       "type": "keyword",
       "include_in_all": false
     },
     "seller": {
       "type": "keyword",
       "include_in_all": false
     },
     "item_title": {
       "type": "text"
     }
   }
 }

В следующем части урока мы обсудим анализаторы и почему отображение необходимо для получения правильных результатов поиска.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *