Основы Elasticsearch

    Elasticsearch — поисковый движок с json rest api, использующий Lucene и написанный на Java. Описание всех преимуществ этого движка доступно на официальном сайте. Далее по тексту будем называть Elasticsearch как ES.


    Подобные движки используются при сложном поиске по базе документов. Например, поиск с учетом морфологии языка или поиск по geo координатам.


    В этой статье я расскажу про основы ES на примере индексации постов блога. Покажу как фильтровать, сортировать и искать документы.


    Чтобы не зависеть от операционной системы, все запросы к ES я буду делать с помощью CURL. Также есть плагин для google chrome под названием sense.


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


    Установка ES


    Для этого нам сначала потребуется Java. Разработчики рекомендуют установить версии Java новее, чем Java 8 update 20 или Java 7 update 55.


    Дистрибутив ES доступен на сайте разработчика. После распаковки архива нужно запустить bin/elasticsearch. Также доступны пакеты для apt и yum. Есть официальный image для docker. Подробнее об установке.


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


    # для удобства запомним адрес в переменную
    #export ES_URL=$(docker-machine ip dev):9200
    export ES_URL=localhost:9200
    
    curl -X GET $ES_URL

    Нам придет приблизительно такой ответ:


    {
      "name" : "Heimdall",
      "cluster_name" : "elasticsearch",
      "version" : {
        "number" : "2.2.1",
        "build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
        "build_timestamp" : "2016-03-09T09:38:54Z",
        "build_snapshot" : false,
        "lucene_version" : "5.4.1"
      },
      "tagline" : "You Know, for Search"
    }

    Индексация


    Добавим пост в ES:


    # Добавим документ c id 1 типа post в индекс blog.
    # ?pretty указывает, что вывод должен быть человеко-читаемым.
    
    curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
    {
      "title": "Веселые котята",
      "content": "<p>Смешная история про котят<p>",
      "tags": [
        "котята",
        "смешная история"
      ],
      "published_at": "2014-09-12T20:44:42+00:00"
    }'
    

    ответ сервера:


    {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_version" : 1,
      "_shards" : {
        "total" : 2,
        "successful" : 1,
        "failed" : 0
      },
      "created" : false
    }
    

    ES автоматически создал индекс blog и тип post. Можно провести условную аналогию: индекс — это база данных, а тип — таблица в этой БД. Каждый тип имеет свою схему — mapping, также как и реляционная таблица. Mapping генерируется автоматически при индексации документа:


    # Получим mapping всех типов индекса blog
    curl -XGET "$ES_URL/blog/_mapping?pretty"

    В ответе сервера я добавил в комментариях значения полей проиндексированного документа:


    {
      "blog" : {
        "mappings" : {
          "post" : {
            "properties" : {
              /* "content": "<p>Смешная история про котят<p>", */ 
              "content" : {
                "type" : "string"
              },
              /* "published_at": "2014-09-12T20:44:42+00:00" */
              "published_at" : {
                "type" : "date",
                "format" : "strict_date_optional_time||epoch_millis"
              },
              /* "tags": ["котята", "смешная история"] */
              "tags" : {
                "type" : "string"
              },
              /*  "title": "Веселые котята" */
              "title" : {
                "type" : "string"
              }
            }
          }
        }
      }
    }

    Стоит отметить, что ES не делает различий между одиночным значением и массивом значений. Например, поле title содержит просто заголовок, а поле tags — массив строк, хотя они представлены в mapping одинаково.
    Позднее мы поговорим о маппинге более подобно.


    Запросы


    Извлечение документа по его id:


    # извлечем документ с id 1 типа post из индекса blog
    curl -XGET "$ES_URL/blog/post/1?pretty"

    {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_version" : 1,
      "found" : true,
      "_source" : {
        "title" : "Веселые котята",
        "content" : "<p>Смешная история про котят<p>",
        "tags" : [ "котята", "смешная история" ],
        "published_at" : "2014-09-12T20:44:42+00:00"
      }
    }

    В ответе появились новые ключи: _version и _source. Вообще, все ключи, начинающиеся с _ относятся к служебным.


    Ключ _version показывает версию документа. Он нужен для работы механизма оптимистических блокировок. Например, мы хотим изменить документ, имеющий версию 1. Мы отправляем измененный документ и указываем, что это правка документа с версией 1. Если кто-то другой тоже редактировал документ с версией 1 и отправил изменения раньше нас, то ES не примет наши изменения, т.к. он хранит документ с версией 2.


    Ключ _source содержит тот документ, который мы индексировали. ES не использует это значение для поисковых операций, т.к. для поиска используются индексы. Для экономии места ES хранит сжатый исходный документ. Если нам нужен только id, а не весь исходный документ, то можно отключить хранение исходника.


    Если нам не нужна дополнительная информация, можно получить только содержимое _source:


    curl -XGET "$ES_URL/blog/post/1/_source?pretty"

    {
      "title" : "Веселые котята",
      "content" : "<p>Смешная история про котят<p>",
      "tags" : [ "котята", "смешная история" ],
      "published_at" : "2014-09-12T20:44:42+00:00"
    }
    

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


    # извлечем только поле title
    curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"

    {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_version" : 1,
      "found" : true,
      "_source" : {
        "title" : "Веселые котята"
      }
    }

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


    curl -XPUT "$ES_URL/blog/post/2" -d'
    {
      "title": "Веселые щенки",
      "content": "<p>Смешная история про щенков<p>",
      "tags": [
        "щенки",
        "смешная история"
      ],
      "published_at": "2014-08-12T20:44:42+00:00"
    }'

    curl -XPUT "$ES_URL/blog/post/3" -d'
    {
      "title": "Как у меня появился котенок",
      "content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
      "tags": [
        "котята"
      ],
      "published_at": "2014-07-21T20:44:42+00:00"
    }'

    Сортировка


    # найдем последний пост по дате публикации и извлечем поля title и published_at
    curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
    {
      "size": 1,
      "_source": ["title", "published_at"],
      "sort": [{"published_at": "desc"}]
    }'

    {
      "took" : 8,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "failed" : 0
      },
      "hits" : {
        "total" : 3,
        "max_score" : null,
        "hits" : [ {
          "_index" : "blog",
          "_type" : "post",
          "_id" : "1",
          "_score" : null,
          "_source" : {
            "title" : "Веселые котята",
            "published_at" : "2014-09-12T20:44:42+00:00"
          },
          "sort" : [ 1410554682000 ]
        } ]
      }
    }

    Мы выбрали последний пост. size ограничивает кол-во документов в выдаче. total показывает общее число документов, подходящих под запрос. sort в выдаче содержит массив целых чисел, по которым производится сортировка. Т.е. дата преобразовалась в целое число. Подробнее о сортировке можно прочитать в документации.


    Фильтры и запросы


    ES с версии 2 не различает фильты и запросы, вместо этого вводится понятие контекстов.
    Контекст запроса отличается от контекста фильтра тем, что запрос генерирует _score и не кэшируется. Что такое _score я покажу позже.


    Фильтрация по дате


    Используем запрос range в контексте filter:


    # получим посты, опубликованные 1ого сентября или позже
    curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
    {
      "filter": {
        "range": {
          "published_at": { "gte": "2014-09-01" }
        }
      }
    }'

    Фильтрация по тегам


    Используем term query для поиска id документов, содержащих заданное слово:


    # найдем все документы, в поле tags которых есть элемент 'котята'
    curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
    {
      "_source": [
        "title",
        "tags"
      ],
      "filter": {
        "term": {
          "tags": "котята"
        }
      }
    }'

    {
      "took" : 9,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "failed" : 0
      },
      "hits" : {
        "total" : 2,
        "max_score" : 1.0,
        "hits" : [ {
          "_index" : "blog",
          "_type" : "post",
          "_id" : "1",
          "_score" : 1.0,
          "_source" : {
            "title" : "Веселые котята",
            "tags" : [ "котята", "смешная история" ]
          }
        }, {
          "_index" : "blog",
          "_type" : "post",
          "_id" : "3",
          "_score" : 1.0,
          "_source" : {
            "title" : "Как у меня появился котенок",
            "tags" : [ "котята" ]
          }
        } ]
      }
    }

    Полнотекстовый поиск


    Три наших документа содержат в поле content следующее:


    • <p>Смешная история про котят<p>
    • <p>Смешная история про щенков<p>
    • <p>Душераздирающая история про бедного котенка с улицы<p>

    Используем match query для поиска id документов, содержащих заданное слово:


    # source: false означает, что не нужно извлекать _source найденных документов
    curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
    {
      "_source": false,
      "query": {
        "match": {
          "content": "история"
        }
      }
    }'

    {
      "took" : 13,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "failed" : 0
      },
      "hits" : {
        "total" : 3,
        "max_score" : 0.11506981,
        "hits" : [ {
          "_index" : "blog",
          "_type" : "post",
          "_id" : "2",
          "_score" : 0.11506981
        }, {
          "_index" : "blog",
          "_type" : "post",
          "_id" : "1",
          "_score" : 0.11506981
        }, {
          "_index" : "blog",
          "_type" : "post",
          "_id" : "3",
          "_score" : 0.095891505
        } ]
      }
    }

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


    Поле _score показывает релевантность. Если запрос выпоняется в filter context, то значение _score всегда будет равно 1, что означает полное соответствие фильтру.


    Анализаторы


    Анализаторы нужны, чтобы преобразовать исходный текст в набор токенов.
    Анализаторы состоят из одного Tokenizer и нескольких необязательных TokenFilters. Tokenizer может предшествовать нескольким CharFilters. Tokenizer разбивают исходную строку на токены, например, по пробелам и символам пунктуации. TokenFilter может изменять токены, удалять или добавлять новые, например, оставлять только основу слова, убирать предлоги, добавлять синонимы. CharFilter — изменяет исходную строку целиком, например, вырезает html теги.


    В ES есть несколько стандартных анализаторов. Например, анализатор russian.


    Воспользуемся api и посмотрим, как анализаторы standard и russian преобразуют строку "Веселые истории про котят":


    # используем анализатор standard       
    # обязательно нужно перекодировать не ASCII символы
    curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"

    {
      "tokens" : [ {
        "token" : "веселые",
        "start_offset" : 0,
        "end_offset" : 7,
        "type" : "<ALPHANUM>",
        "position" : 0
      }, {
        "token" : "истории",
        "start_offset" : 8,
        "end_offset" : 15,
        "type" : "<ALPHANUM>",
        "position" : 1
      }, {
        "token" : "про",
        "start_offset" : 16,
        "end_offset" : 19,
        "type" : "<ALPHANUM>",
        "position" : 2
      }, {
        "token" : "котят",
        "start_offset" : 20,
        "end_offset" : 25,
        "type" : "<ALPHANUM>",
        "position" : 3
      } ]
    }

    # используем анализатор russian
    curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"

    {
      "tokens" : [ {
        "token" : "весел",
        "start_offset" : 0,
        "end_offset" : 7,
        "type" : "<ALPHANUM>",
        "position" : 0
      }, {
        "token" : "истор",
        "start_offset" : 8,
        "end_offset" : 15,
        "type" : "<ALPHANUM>",
        "position" : 1
      }, {
        "token" : "кот",
        "start_offset" : 20,
        "end_offset" : 25,
        "type" : "<ALPHANUM>",
        "position" : 3
      } ]
    }

    Стандартный анализатор разбил строку по пробелам и перевел все в нижний регистр, анализатор russian — убрал не значимые слова, перевел в нижний регистр и оставил основу слов.


    Посмотрим, какие Tokenizer, TokenFilters, CharFilters использует анализатор russian:


    {
      "filter": {
        "russian_stop": {
          "type":       "stop",
          "stopwords":  "_russian_"
        },
        "russian_keywords": {
          "type":       "keyword_marker",
          "keywords":   []
        },
        "russian_stemmer": {
          "type":       "stemmer",
          "language":   "russian"
        }
      },
      "analyzer": {
        "russian": {
          "tokenizer":  "standard",
          /* TokenFilters */
          "filter": [
            "lowercase",
            "russian_stop",
            "russian_keywords",
            "russian_stemmer"
          ]
          /* CharFilters отсутствуют */
        }
      }
    }

    Опишем свой анализатор на основе russian, который будет вырезать html теги. Назовем его default, т.к. анализатор с таким именем будет использоваться по умолчанию.


    {
      "filter": {
        "ru_stop": {
          "type":       "stop",
          "stopwords":  "_russian_"
        },
        "ru_stemmer": {
          "type":       "stemmer",
          "language":   "russian"
        }
      },
      "analyzer": {
        "default": {
          /* добавляем удаление html тегов */
          "char_filter": ["html_strip"],
          "tokenizer":  "standard",
          "filter": [
            "lowercase",
            "ru_stop",
            "ru_stemmer"
          ]
        }
      }
    }

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


    Создание индекса


    Выше мы описали default анализатор. Он будет применяться ко всем строковым полям. Наш пост содержит массив тегов, соответственно, теги тоже будут обработаны анализатором. Т.к. мы ищем посты по точному соответствию тегу, то необходимо отключить анализ для поля tags.


    Создадим индекс blog2 с анализатором и маппингом, в котором отключен анализ поля tags:


    curl -XPOST "$ES_URL/blog2" -d'
    {
      "settings": {
        "analysis": {
          "filter": {
            "ru_stop": {
              "type": "stop",
              "stopwords": "_russian_"
            },
            "ru_stemmer": {
              "type": "stemmer",
              "language": "russian"
            }
          },
          "analyzer": {
            "default": {
              "char_filter": [
                "html_strip"
              ],
              "tokenizer": "standard",
              "filter": [
                "lowercase",
                "ru_stop",
                "ru_stemmer"
              ]
            }
          }
        }
      },
      "mappings": {
        "post": {
          "properties": {
            "content": {
              "type": "string"
            },
            "published_at": {
              "type": "date"
            },
            "tags": {
              "type": "string",
              "index": "not_analyzed"
            },
            "title": {
              "type": "string"
            }
          }
        }
      }
    }'

    Добавим те же 3 поста в этот индекс (blog2). Я опущу этот процесс, т.к. он аналогичен добавлению документов в индекс blog.


    Полнотекстовый поиск с поддержкой выражений


    Познакомимся с еще одним типом запросов:


    # найдем документы, в которых встречается слово 'истории'
    # query -> simple_query_string -> query содержит поисковый запрос
    # поле title имеет приоритет 3
    # поле tags имеет приоритет 2
    # поле content имеет приоритет 1
    # приоритет используется при ранжировании результатов
    curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
    {
      "query": {
        "simple_query_string": {
          "query": "истории",
          "fields": [
            "title^3",
            "tags^2",
            "content"
          ]
        }
      }
    }'

    Т.к. мы используем анализатор с русским стеммингом, то этот запрос вернет все документы, хотя в них встречается только слово 'история'.


    Запрос может содержать специальные символы, например:


    "\"fried eggs\" +(eggplant | potato) -frittata"

    Синтаксис запроса:


    + signifies AND operation
    | signifies OR operation
    - negates a single token
    " wraps a number of tokens to signify a phrase for searching
    * at the end of a term signifies a prefix query
    ( and ) signify precedence
    ~N after a word signifies edit distance (fuzziness)
    ~N after a phrase signifies slop amount

    # найдем документы без слова 'щенки'
    curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
    {
      "query": {
        "simple_query_string": {
          "query": "-щенки",
          "fields": [
            "title^3",
            "tags^2",
            "content"
          ]
        }
      }
    }'
    
    # получим 2 поста про котиков

    Ссылки



    PS


    Если интересны подобные статьи-уроки, есть идеи новых статей или есть предложения о сотрудничестве, то буду рад сообщению в личку или на почту m.kuzmin+habr@darkleaf.ru.

    Поделиться публикацией

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

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

      0
      По опыту, на сколько эффективнее выборки MySQL? Или можно как-то объединить: хранение mysql, поиск ES, даст ли это преимущества? Не придумаю никак сравнительный тест.
        0
        По опыту: можно в MySQL хранить данные (например, контактные данные миллиона человек), а с помощью ElasticSearch организовать нечёткий поиск по именам, фамилиям, номерам телефонов и т.д. ElasticSearch вернёт идентификатор, поиск по ID в MySQL будет уже быстрым.
          0
          Можно не только хранить ID, но и поля этого человека и уже не обращаться к mysql. Я упоминал об этом в статье.
            0
            Можно конечно, если нам например нужно вывести какую-то условно «карточку».
          +2
          Mysql — это надежное хранилище, источник исходных данных, ES — поисковый движок. У них разные задачи.
          В большинстве случаев используют Mysql + ES. Бывают случаи, когда не очень важные данные хранят сразу в ES, например логи.
          ES может забрать на себя нагрузку на чтение с Mysql, т.е. пишем в Mysql, а читаем из ES. ES горизонтально масштабируется значительно проще, чем mysql, т.к. есть автоматический шардинг.
          Надо смотреть на конкретные запросы и вполне возможно, что mysql при выборке по первичному ключу будет быстрее.
          Но нужно понимать, что в ES есть значительно больше типов запросов, чем в mysql, например поиск по geo координатам, с помощью плагинов можно сделать поиск по изображениям.
          ПС. под mysql тут можно понимать любую реляционную БД.
            0
            Спасибо! Нет ли чего почитать по успешной реализации подобной связки? В Ваших публикациях, к сожалению, такого нет :(
            З.Ы. только начал вкуривать ES.
              0
              Для начала этого урока будет достаточно. Ваше приложение после записи в mysql будет писать данные в ES. Т.е. взаимодействие идет через приложение.
              Я советую потом прочитать официальную документацию, она хорошо написана и там есть классные примеры.
                0
                Поковыряемс :)
                0
                Я такое делал на рельсах пару лет назад, получилось весьма интересно, работает до сих пор, нареканий нет. Если интересно в контексте Rails, могу поделиться сниппетами.
              0
              Использую связку mysql + elasticsearch для хранения и поиска по закладкам. На приблизительно 500к записей разница получается приблизительно раз в 10-15. Запись ищется в elasticsearch он возвращает id, а далее mysql достает по id саму запись из базы. Сравнение конечно несколько не правильное, ибо в elastic'е используется стемминг, а в mysql просто поиск по like'у, и теоретически можно попытаться настроить лучше. Вопрос в том нужно ли это. По факту писать фильтры через elasticsearch получается удобней. Из минусов elasticsearch любит память.
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Используется добрый LIKE '%search%' точнее по факту там чуть похитрее но в целом да ^_^
                  Match against не использую, ибо иногда приходится искать маленькие слова, а оно их вроде как не любит, по крайней мере раньше не любило :)
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      ну вот, а еще там стемминг не полноценный :) нет конечно всегда можно вручную преобразовать все слова.
                      Еще одним из плюсов elasticsearch'а можно посчитать возможность удобно делать фасетный поиск. А еще elasticsearch неплохо может горизонтально масштабироваться. Не уверен что производительность match against тоже так можно поднимать, но это чисто мысли, ибо пол миллиона записей даже с десятком полей это ни о чем практически в любой бд если не косячить.
                      В общем у всех свои задачи :)
                      Там где стемминг и фасетный поиск не нужны используется как раз match against, на скорость что характерно жалоб нет. А для закладок использую elastic удобно получается. Так что применимость у всего своя. И если вас все устраивает то внешний поиск действительно не нужен.
                  +2
                  Для эластика есть морфологические плагины, которые больше, чем стемминг, например «люди» и «человек» будут приведены к одному и тому же токену. Посмотрите в сторону плагина russian_morphology, например. Хотя в нем встречаются забавные артефакты. Плюс возможность поиска с расстоянием между словами (с количеством перестановок, если точнее) и другие прелести Lucene.
                  Память бывает разная, но все базы данных так или иначе любят память. Например, сейчас в эластике уже можно настроить использование doc_values для необходимых полей, в этом случае куча уже может использоваться меньше при выполнении запросов, а данные будут маппиться в память и при необходимости выкидваться опять же средствами оси.
                    0
                    у меня сейчас следующая связка фильтров — 'lowercase', 'russian_morphology', 'english_morphology', 'ru_stopwords', 'ru_stemming', человек — люди вполне неплохо отрабатывает. На тему поиска с расстоянием думаю в будущем подкрутить, когда подсказки к запросам буду делать, пока как то не надо. в основном или поиск по словам или фасеты. Да и эластик сейчас староват надо будет обновлять, но для этого же время надо будет выделить чтобы все переписать :)
                      0
                      так у вас же включен russian_morphology ) Уберите его, и человек/люди расползутся в разные токены.
                        0
                        :) а зачем, специально же добавлялось.
                        правда я предполагал что он все же больше определяет однокоренные слова, то есть такой продвинутый стемминг, так что спасибо за просвещение.
                          0
                          в изначальном комментарии говорилось, что эластик использует стемминг. Я лишь уточнил, что токенизация не ограничивается стеммингом )
                      0
                      Про doc_values могу сказать, что на hdd сильно просаживает индексацию, но память таки да, экономит. На ssd не использовали, но вроде не должно быть такого.
                        0
                        А какая нагрузка индексации и структура кластера? Не замечал заметного снижения производительности при использовании doc_values (как раз-таки на hdd, правда SAS 10K), в моем случае скорость индексации была до 2К/сек (не пиковая, а фактическая), запись производилась на ноды, расположенные на 3 серверах
                          0
                          Тестировали даже не на той версии, что сейчас используем, но всё же тогда надо было решить "брать или не брать". На тестовой машине с ES 2.0 doc_values просадил скорость индексации где-то на 15%, решили тогда отказаться. Сейчас наверно лучше, не знаю.
                          Кластер большой, к тому же по паре инстансов для обхода "ограничения" размера хипа в 32гб. Скорость 20-25к/с в среднем. Так что суммарно я думаю могло (может и сейчас) сильно просадить индексацию. А ещё сжатие включили, что сейчас оказалось пустой тратой, как мне кажется…
                          Сейчас уже подумываем взять обратно doc_values, на другом кластере для меньшего объёма данных.
                      0
                      использовать ES для поиска имеет смысл в том случае, когда запросы к РСУБД невозможно построить оптимально.
                      а примере всеми любимого MySQL и условно кругловакуумного блога. Когда нам нужно отобрать посты нескольких авторов за промежуток времени. MySQL не может использовать индекс для поиска по 2 range условиям. Если у вас пара тысяч записей, то это не страшно. Если у вас пара сотен тысяч — миллионы записей, то MySQL уйдет в себя всерьез и надолго. Кеширование тоже не всегда спасет, ибо параметров может быть сильно больше двух, и в каждом десятки-сотни вариантов (фильтры в интернет магазинах). Количество комбинаций такое, что никакой памяти не хватит.
                      Вот тут ES покажет себя во всей красе. Вы можете очень быстро искать по любой комбинации параметров и получить айдишники записей. Если вам нужно показать сколько в магазине товаров с такой комбинацией параметров, то этого уже достаточно(щелкаешь ОЗУ: 1Гб — видишь плашечку: найдено 100 товаров. Ставишь еще галочку возле 2Гб — плашечка сразу показывает: найдено 200 товаров). Если вам нужно показать клиенту список товаров, то тут уже запрос к мускуля по праймари кей. Очень бысто и еффективно. Тут же поможет и кеш по айдишнику товара и репликация мускуля, если мускуль не вытягивает нагрузку на чтение, но то уже совсем другая история
                        0
                        А если в MySQL просто создать индекс по всем нужным полям — это не поможет, потому что у нас в запросе RANGE, или просто памяти займёт слишком много?
                      0
                      У нас ES используется как полноценная БД из-за мастабируемости и распределённого поиска, плюс к этому, за счёт кластера достигается высокая пропускная способность, а также лёгкость добавления ноды. Вроде бы PostgreSQL кластер тоже умеет, но не знаю как у него с вышеперечисленным, не говоря уже про MySQL. Но в ES нет такого мощного SQL, какой он есть в РБД. Так что каждому своё.
                      Конечно используем РБД, но скорее для вставки из ES для других приложений, которые используют их для своей работы.
                        0
                        Тут нужно быть осторожным, тк ES может потерять данные, особенно в кластерной конфигурации.
                        https://aphyr.com/posts/317-jepsen-elasticsearch
                        Я делал перевод статьи по ES как NoSQL базу — https://habrahabr.ru/company/percolator/blog/222765/
                        Есть даже проект Crate.io. Это что-то вроде sql над es: https://crate.io/a/how-is-crate-data-different-than-elasticsearch/
                        Postgresql из коробки не умеет кластер, есть PostgresXL но это немного не о том.
                          0
                          А как насчёт 2.х? Мы используем 2.1, а последняя сейчас вообще 2.3.
                          По поводу sql, то мы используем ES по назначению — поиск документов, поэтому нет потребности в каких-то сложных выборках или джойнах.
                            0
                            Распределенная система подвержена сетевым сбоям и разделению кластера. Что произойдет если кластер разделится на 2 половины и будет происходить запись в обе половины? Это условный пример, и нужно смотреть как ES поведет себя в этой ситуации, наверняка в меньшей части кластера мастер не будет выбран и запись не пойдет.
                            Я просто хочу обратить внимание, что с распределенными системами не все так просто. И придется чем-то жертвовать.
                            В сети есть много материалов, тот же Aphyr. Можно погуглить материалы про cap теорему.
                              0
                              Это всё понятно, что оно может так случиться. У нас такого ешё не наблюдал, поэтому не знаю. Но я думаю лучше было бы действительно остановить запись (и тогда сработают всякие сигнализации) и не терять данные, тогда это не будет проблемой. Техническую проблему можно устранить и продолжить запись, а потерянные данные не вернёшь.
                                0
                                Можно в настройках настроить минимальное количество мастеров для функционирования кластера, чтобы избежать сплитбрейна
                        0
                        промахнулся
                          0
                          Мы тоже используем ЕС как полноценную базу данных для документов (в терминах NoSQL). Клиенты и админы очень довольны.
                          Кстати, ES вместе с Кибаной официально рекомендуются Амазоном. Про предел индексации в 34К в секунду (тот же Кинетик гарантирует 1К в секунду на шард) на ноду тут уже писали.
                            0
                            Почему бы не использовать в качестве базы данных базу данных (mongodb). Насколько я понял из докладов, ЕS, как база данных, во всём ему проигрывает, кроме опций поиска. Мы используем их вместе, хранение в mongodb, поиск — в ES.
                              0
                              Потому что Mongo не Lucene. Нам так надо.
                            0
                            Сейчас уже не совсем правильно называть ES поисковым движком. Это уже больше аналитическая система с широкими возможностями агрегации данных.
                              0
                              Все-таки ES — это поисковый движок, а вот стек ELK — аналитическая система
                              0
                              Подскажите, а какой алгоритм лучше исопльзовать для следующего — есть большой каталог товаров с параметрами, изначально хранится в mysql. Происходит поиск по базе эластика и в ответ выдаётся какой-либо результат (набор товаров). Как организовать постраничную навигацию по результатам? Средствами эластика или передавать коды товаров в sql-запрос и данные для конкретной страницы брать из mysql?
                                0
                                Если наладить хранение документов в эластике целиком, то необходимость во втором запросе в mysql отпадет
                                  0
                                  ES умеет постраничную выдачу. В этой статье есть секция "Сортировка", в ней используется ключ size.
                                  Так же с помощью Scroll можно избежать проблемы постраничной выдачи, когда между получением страниц происходит вставка. Т.е. мы выбираем первую страницу, в кто-то добавил документ, который мог быть на первой странице, соответственно все документы съедут и последний документ с первой страницы продублируется и встанет на первое место на вторую страницу. А Scroll делает снимок и новый документ в выдачу не попадет. Надеюсь понятно объяснил)
                                  https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html
                                    0
                                    Да, более-менее, спасибо
                                      0
                                      Использование скроллов для постраничной выдачи пользователю — не очень хорошая идея. Скролл между запросами будет висеть в памяти. А по истечении указанного срока жизни пропадет. Если пользователь «засиделся» на странице, то следующую он может не получить.
                                        0
                                        Согласен, я упомянул скролл, чтобы просто показать эту возможность.
                                        0
                                        Только этот скролл медленный тормоз, на больших объемах будете ждать сутками.
                                        0
                                        Кстати, если в магазине предусмотрены фильтрации по нескольким типам признаков я бы сразу порекомендовал обратить внимание на фасеты в Elastic — просто отличная штука!
                                          0
                                          fasets начиная со второй версии выпили, теперь только агрегация https://habrahabr.ru/company/smartprogress/blog/227131/
                                            0
                                            В своем сервисе для поиска по товарам магазина успешно используем аггрегацию. Проблемы были только с вложенными структурами (категории), но изменив модель в ES проблема ушла.
                                          0
                                          У меня такой вопрос: это что же за блог, для индексации которого применяется Elasticsearch? Какая же у вас посещаемость?)
                                            +2
                                            Да это же просто пример на кошечках и собачках)
                                            Не в посещаемости дело, а в возможностях поиска.
                                              +1
                                              Например, wordpress.org, в котором на ES сделан поиск по всем блогам, созданным в системе.
                                              +2
                                              Второй большой проект с высокой нагрузкой и распределённой системой по датацентрам полностью на эластике делаем, 2 года — полёт нормальный. Пришлось многое осознать как правильно всё это готовить, но результат очень приятный, ведь основная суть архитектуры, что независимо какой у тебя объём данных храниться, поиск по ним будет одинакового быстрый из-за распределённости.
                                                +4
                                                Как вы шардируете данные?
                                                Заводите новый индекс, через определенный интервал времени?
                                                Просто на горячую изменить кол-во шардов для индекса невозможно.
                                                Наверняка используете роутинг, т.к. при увеличении кол-ва данных будет падать скорость поиска.
                                                Расскажите, пожалуйста, про "правильную готовку".
                                                  0
                                                  Роутинг вводили, потом выводили с трудом. Не везде его можно использовать, он очень сильно привязывает всё к знанию этого самого роута, а не везде это возможно знать изначально в запросе.
                                                  Система спроектирована так, что у нас есть версионность и разделение по типам, например users у нас как индекс {company}_users_v{1..999} в нём есть типы admin, operator, engineer, abonent и т.п.
                                                  Индексы связаны между собой через alias как {company}_users => {company}_users_*
                                                  0
                                                  А как это всё переживает разрыв связи между датацентрами?
                                                    0
                                                    Точно так же как и в любом другом кластере, срабатывает failover.
                                                    0
                                                    А у вас бывают очень тяжеловесные запросы, которые тормозят всю систему? Мы все никак не можем решить такую проблему: есть запросы пользовательские, которые должны отрабатываться быстро, а есть админские, которые по минуте выполняются. При этом во время выполнения админских запросов весь индекс тормозит. Как бы так понизить приоритет одним запросам, и поднять другим?
                                                      0
                                                      можно разнести нагрузку по разным репликам
                                                      например, явно обращаясь к реплике https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-preference.html
                                                      наверняка есть и другие варианты
                                                        0
                                                        Пробовал через preference, и при этом все равно необъяснимо тормозит.
                                                        0
                                                        Для начало нужно понять из-за чего именно такое происходит, сделайте explain или лучше profile, чтобы понять на что уходит время. А после уже будет видно в чём проблема и можно подумать над её решением.
                                                        0
                                                        Напишите пост про боевое применение Эластика, многим очень интересно будет.)
                                                      0
                                                      Объясните мне, дилетанту в области nosql, два момента в отличиях Mongodb, ES и Sphinx:

                                                      1. Что лучше использовать для задач фасетного поиска, например для поиска по параметрам товаров интернет-магазина?

                                                      2. Есть ли в Mongodb полнотекстовый поиск с учетом русской морфологии и с ранжированием результатов? Если да, то зачем тогда использовать ES и Sphinx? Они реально быстрее?

                                                      Давно присматриваюсь к nosql-решениям и никак не могу сделать выбор между Mongodb, ES и Sphinx именно в разрезе этих двух задач: полнотекстовый и фасетный поиски. Интуиция подсказывает выбор в пользу Mongodb как более зрелого, проверенного и быстрого решения. Но это только интуиция.
                                                        0
                                                        монга это база данных, а эластик и сфинкс — поисковые движки. это разные вещи. Не знаю как там сфинкс, но эластик точно не гарантирует сохранность данных, так что хранить в нем можно лишь данные, которые терять можно (сервисные логи к примеру) или те, которые можно восстановить. я знаю что сфинкс заточен на поиск текста, а вот как в нём с фасетами не знаю. с эластиком точно можно организовать и фасеты и поиск текста. в скорости поиска и эластик и сфинкс обойдут монгу, имхо. Кроме того монга данные на диск скидывает, когда их много. и обращение к этим данным очень медленное, если они вдруг понадобятся
                                                          0
                                                          про эластик ваша информация устарела, это давно уже полноценная nosql БД, имеет свои плюсы и минусы. Данные там потерять можно точно так же как и в любой другой БД, в которой нет транзакций ;)
                                                            0
                                                            я говорю, не о консистентности. Банально уборщица выдернула шнур и все. Есливам неоткуда просинкать данные по новой, то вы их потеряли. полностью. Редис/монга держат (или периодически скидывают) данные на диске. После включение данные поднимутся с диска и все будет ок.
                                                              0
                                                              стоп стоп) вы сами себе противоречите. Если у вас кластер редиса, то проблема будет ровно такой же вы потеряете данные за тот промежуток времени снапошотов, которые установлены в настройках. В эластике всё тоже самое, пишется в одну ноду, потом разноситься по другим, если не успел записать (выдернули свет) они просто потеряются как и в случаи с редисом/монгой. В запросе к эластику на запись можно ожидать записи данных и выставить на эту запись обновления индекса мгновенно без задержки, по умолчанию все записи собираются в pool и раз в секунду переиндексируются.
                                                                0
                                                                да, вы правы. стоит углубить свои знания по еластику
                                                          0
                                                          Если вам нужны фасетки и более-менее нормальная морфология — берите Apache Solr или ElasticSearch. И то, и другое использует поисковый движок Apache Lucene под капотом.

                                                          В случае Solr'а при использовании docValues стоит брать 4.10 или подождать 5.6. Или использовать поля с docValues=false, тогда проблемы нет. Если говорить про интернет-магазин, то вариативность данных для каждой из фасеток, скорее всего, небольшая (сотни-тысячи значений), так что требования по памяти для UnInvertedField кэша будут небольшие.

                                                          Авторизованные данные, имхо, стоит хранить снаружи (mysql, postgres, mongo — куда лучше лягут по структуре), чтобы иметь возможность переиндексировать, экспериментировать с индексами (например, вкрутить кастомную морфологию) и т. п.

                                                          Для нормализации порекомендую попробовать AOT'овскую морфорлогию.
                                                          0
                                                          Расскажите, а можно ли в ES делать что-то типа join'а по разным индексам или типам? И как это вообще работает?
                                                          Насколько я понимаю, типичный сценарий для ES следующий. Каждый день (или через другой промежуток времени) создается новый индекс, который содержит данные за данный период времени. Допустим, что я сохраняю логи в ES. Получается, что в терминах ES таблица логов — это отдельный тип (type). Теперь допустим, что мне надо в другом типе сохранять какую-то внешнюю информацию (например маппинг между ip адресом и страной, где размещается сервер логов). Для этого, насколько я понимаю, нужно создать новый тип с этими данные. В этом случае мне интересно:
                                                          1) как организовать join между двумя типами
                                                          2) в случае создания нового индекса, нужно пересоздавать тип маппинга. Как обычно избавляются от избыточной информации в этом случае?
                                                            0
                                                            Есть parent-child query. Это аналог join.
                                                              0
                                                              Нельзя организовать join между двумя типами, потому что данные могут находиться на разных шардах. Обычно в индекс просто записывается избыточная информация. При создании новых индексов можно использовать шаблоны.
                                                              Но в целом, если вы уже взялись за ES, привыкайте к денормализации.
                                                                0
                                                                Делать join между несколькими шардами нельзя. Но при индексации можно указать parent и оба документа окажутся на одном шарде.
                                                                  0
                                                                  Конечно. И это не единственный способ — в руководстве рекомендуют выбрать один из четырех: join на стороне приложения, денормализация, вложенные документы и parent-child. Каждый хорош для своих целей, но мне показалось, что для исходной задачи ("например маппинг между ip адресом и страной, где размещается сервер логов") проще всего было бы применить денормализацию.
                                                                0
                                                                В вашем случаи нужно просто перестроить мышление на денормализацию данных и подобные вопросы вообще не станут возникать.
                                                                0
                                                                Отличная «get started» статья получилась у вас!
                                                                Прошелся по всем примерам и действительно есть теперь общее представление в первом приближении.

                                                                Можете только подсказать момент, какой вообще лучше метод, чтобы загонать данные в индекс для уже существующего приложения (на php-mysql)? Сделать скрипт и поштучно (либо какими-то порциями) закидывать? И потом при редактировании, удалении, добавлении нового уже в своей бизнес логике обращаться к эластику? Либо перепарсивать как-то все периодически. Я слышал про «river», но не особо понял как он работает.
                                                                  0
                                                                  Спасибо!

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

                                                                  River стал deprecated.

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

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