Elastic Search, быстрый старт

31 янв. 2018 г.

На текущем проекте используем Elastic Search. Elastic представляет собой движок для продвинутого поиска по документам. Основан на Apache Solr. По сути, является NoSQL хранилищем, и некоторые даже используют его, как отдельную БД.

За время работы нашлось несколько вещей, которые не сразу удалось настроить. Информации в сети много, но местами она не очень структурирована, либо есть различия для разных версий. Либо просто сразу не удается найти нужный материал. В результате пришлось перелопатить весь интернет, прежде чем все заработало как требовалось.

Это первая статья из цикла. Здесь все тривиально, речь пойдет о базовых настройках Elastic Search.

Установка

Установить elastic search на ubuntu можно по этому руководству.

Или через docker.

docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.1.0 -->

Вариант с установкой через docker мне понравился гораздо больше т.к. сам процесс гораздо проще. Можно без труда установить любую актуальную версию. Не надо вносить изменения в хост систему. Вообще, всем рекомендую. Сначало немного сложно, но потом очень удобно.

Предыдушие версии Elastic search можно найти здесь: https://hub.docker.com/_/elasticsearch/ Например, elastic 5.6 запускается такой командой:

docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:5.6.5

Чтобы проверить работоспособность, достаточно открыть в браузере: http://localhost:9200/

И увидеть что-то такое:

{
  "name": "s47BTOo",
  "cluster_name": "docker-cluster",
  "cluster_uuid": "G_58RLKfS4C3ZbxnEpfdiw",
  "version": {
    "number": "6.1.2",
    "build_hash": "5b1fea5",
    "build_date": "2018-01-10T02:35:59.208Z",
    "build_snapshot": false,
    "lucene_version": "7.1.0",
    "minimum_wire_compatibility_version": "5.6.0",
    "minimum_index_compatibility_version": "5.0.0"
  },
  "tagline": "You Know, for Search"
}

Далее в статье я буду использовать elastic search 6.1.2. Для других версий настройки могут немного отличаться.

Добавление индекса и маппинга

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

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

curl -X PUT \
  http://localhost:9200/product \
  -H 'Content-Type: application/json' \
  -d '{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "шуруповерт, отвертка"
          ]
        },
        "ru_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "ru_stemmer": {
          "type": "stemmer",
          "language": "russian"
        }
      },
      "analyzer": {
        "my_synonyms": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "my_synonym_filter",
            "ru_stop",
            "ru_stemmer"
          ]
        }
      }
    }
  }
}'

Ответ должен быть примерно таким:

{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "product"
}

Далее добавляем mapping:

curl -X PUT \
  http://localhost:9200/product/_mapping/type \
  -H 'Content-Type: application/json' \
  -d '{
   "properties": {
      "id": {
          "type": "integer"
        },
        "sku": {
          "type": "text",
          "index": true,
          "fielddata": true,
          "search_analyzer": "my_synonyms",
         "analyzer": "my_synonyms",
         "term_vector": "with_positions_offsets_payloads"
        },
        "name": {
          "type": "text",
          "index": true,
          "fielddata": true,
          "search_analyzer": "my_synonyms",
         "analyzer": "my_synonyms",
         "term_vector": "with_positions_offsets_payloads"
        },
        "description": {
          "type": "text",
          "index": true,
          "fielddata": true,
          "search_analyzer": "my_synonyms",
         "analyzer": "my_synonyms",
         "term_vector": "with_positions_offsets_payloads"
        },
        "price": {
          "type": "double"
        },
        "created_at": {
          "type": "text",
          "index": true,

          "fielddata": true,
          "search_analyzer": "my_synonyms",
         "analyzer": "my_synonyms",
         "term_vector": "with_positions_offsets_payloads"
        },
        "updated_at": {
          "type": "text",
          "index": true,

          "fielddata": true,
          "search_analyzer": "my_synonyms",
         "analyzer": "my_synonyms",
         "term_vector": "with_positions_offsets_payloads"
        }
   }
}'

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

{
  "acknowledged": true
}

Добавление данных

После того, как мы настроили индекс и маппинг, можно приступить к загрузке данных. Если индекс/маппинг не существует, то он будет создан автоматически. Добавим запись в elastic search. Выполним такой запрос:

curl -X PUT \
  http://localhost:9200/product/type/1 \
  -H 'Content-Type: application/json' \
  -d '{
    "id": 1,
    "sku": "ИНТЕРСКОЛ ОА-3,6Ф",
    "name": "Отвертка аккумуляторная ИНТЕРСКОЛ ОА-3,6Ф блистер (433.0.2.00)",
    "description": "Отвертка аккумуляторная ИНТЕРСКОЛ ОА-3,6Ф блистер (433.0.2.00) li-ion Номинальное напряжение, В 3,6 Частота вращения, об/мин 210 Макс. Крутящий момент, Нм 5 Число ступеней регулировки крутящего момента 15+1 Масса, кг 0,5 Особенности: Технология Li-ion, Редуктор с металлическими пластинами, компактность, светодиодный фонарь, индикатор заряда, LED-подсветка.",
    "attribute_set_id": 4,
    "price": 100,
    "created_at": "2017-12-04 10:08:12",
    "updated_at": "2017-12-27 10:28:36"
}'

В результате, продукт добавлен в elastic search:

{
    "_index": "product",
    "_type": "type",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

Получение данных

Получить запись по id можно так:

http://localhost:9200/product/type/1

Получить все записи в индексе можно таким запросом:

http://localhost:9200/product/_search?pretty=true&q=*:*

Поиск

Для более презентативных результатов рекомендую добавить еще несколько(десятков) записей. Хотя, общий принцип можно понять и с двумя записями. Поиск по одному полю:

curl -X POST \
  http://localhost:9200/product/type/_search \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 51c27b84-a128-3d52-aa0e-67f2925c0f28' \
  -d '{
  "query": {
    "match": {
      "sku": "Интерскол"
    }
  }
}
'

Поиск по всем полям:

curl -X POST \
  http://localhost:9200/product/type/_search \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 96ccd985-80cc-835b-885f-0ced99b839c6' \
  -d '{
  "query": {
    "multi_match" : {
      "query": "Отвертка"
    }
  }
}
'

Пагинация

Пагинация:

curl -X POST \
  http://localhost:9200/product/type/_search \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: f698e45a-4680-ae18-554f-a64f3dbbdc44' \
  -d '{
  "from" : 0, "size" : 10,
  "query": {
    "match": {
      "sku": "Интерскол"
    }
  }
}
'

Сортировка

Сортировка:

curl -X POST \
  http://localhost:9200/product/type/_search \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 91153d43-0ee7-b1d3-a283-2043bed98772' \
  -d '{
    "sort" : {
        "sku": {
        "order": "asc",
        "mode":  "min"
      }
    },
    "_source": ["id", "sku","name"],
    "size": 2
}
'

Для корректной работы сортировки нужен настроеный индекс и маппер.

Удаление данных

Если все надоело, можно удалить все данные и попробовать снова:

curl -X DELETE \
  http://localhost:9200/_all \

Либо можно перезапустить docker контейнер. Тогда все данные и настройки сбросятся.

Все запросы из статьи оформлены в виде Postman коллекции. Скачать можно тут: elastic-demo-part1.postman_collection.json.

На этом пока все. В следующих статьях планирую рассказать о настройке морфологического поиска и синонимов. Спасибо за внимание!