Elasticsearch негіздері

Elasticsearch — Java тілінде жазылған Lucene көмегімен json rest api бар іздеу жүйесі. Бұл қозғалтқыштың барлық артықшылықтарының сипаттамасы мына жерден қол жетімді ресми сайт. Келесіде біз Elasticsearch-ті ES деп атаймыз.

Ұқсас қозғалтқыштар құжат деректер базасында күрделі іздеулер үшін қолданылады. Мысалы, тілдің морфологиясын ескере отырып іздеу немесе гео координаттар бойынша іздеу.

Бұл мақалада мен блог жазбаларын индекстеу мысалында ES негіздері туралы айтатын боламын. Мен құжаттарды сүзу, сұрыптау және іздеу жолын көрсетемін.

Операциялық жүйеге тәуелді болмау үшін мен барлық сұрауларды ES-ге CURL арқылы жасаймын. Сондай-ақ Google Chrome плагині бар сезім.

Мәтінде құжаттамаға және басқа көздерге сілтемелер бар. Соңында құжаттамаға жылдам қол жеткізуге арналған сілтемелер бар. Таныс емес терминдердің анықтамаларын мына жерден табуға болады сөздіктер.

ES орнату

Ол үшін бізге алдымен Java керек. Әзірлеушілер ұсынамыз Java 8 жаңарту 20 немесе Java 7 жаңарту 55 қарағанда жаңарақ Java нұсқаларын орнатыңыз.

ES тарату мына жерден қол жетімді әзірлеуші ​​сайты. Мұрағатты ашқаннан кейін іске қосу керек bin/elasticsearch. Сондай-ақ қолжетімді apt және yum үшін пакеттер. Бар докерге арналған ресми сурет. Орнату туралы толығырақ.

Орнатып, іске қосқаннан кейін функционалдылықты тексерейік:

# для удобства запомним адрес в переменную
#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 автоматты түрде жасалады индекс блог және Түрі пост. Шартты аналогияны салуға болады: индекс – мәліметтер қоры, тип – бұл деректер қорындағы кесте. Әрбір түрдің өз схемасы бар - картаға, реляциялық кесте сияқты. Құжат индекстелген кезде салыстыру автоматты түрде жасалады:

# Получим 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 бір мән мен мәндер массивін ажыратпайды. Мысалы, тақырып өрісінде жай тақырып бар, ал тегтер өрісінде жолдар жиымы болады, бірақ олар салыстыруда бірдей түрде көрсетіледі.
Карта жасау туралы кейінірек айтатын боламыз.

сұраулар

Құжатты идентификаторы бойынша шығарып алу:

# извлечем документ с 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 қысылған бастапқы құжатты сақтайды. Егер бізге бүкіл бастапқы құжат емес, тек идентификатор қажет болса, біз бастапқы сақтауды өшіре аламыз.

Қосымша ақпарат қажет болмаса, біз тек _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 шығаруда сұрыптау орындалатын бүтін сандар массиві бар. Анау. күн бүтін санға түрлендірілді. Сұрыптау туралы қосымша ақпаратты мына жерден табуға болады құжаттама.

Сүзгілер мен сұраулар

Оның орнына 2-нұсқадағы ES сүзгілер мен сұрауларды ажыратпайды контексттер ұғымы енгізіледі.
Сұрау мәтінмәні сүзгі мәтінмәнінен ерекшеленеді, себебі сұрау _score жасайды және кэштелмейді. _ұпайдың не екенін кейінірек көрсетемін.

Күні бойынша сүзгілеу

Біз сұранысты қолданамыз диапазон сүзгі контекстінде:

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

Тегтер бойынша сүзгілеу

Біз қолданамыз терминдік сұрау берілген сөзді қамтитын құжат идентификаторларын іздеу үшін:

# найдем все документы, в поле 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" : [ "котята" ]
      }
    } ]
  }
}

Толық мәтінді іздеу

Біздің үш құжатта мазмұн өрісінде мыналар бар:

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

Біз қолданамыз сәйкестік сұрау берілген сөзді қамтитын құжат идентификаторларын іздеу үшін:

# 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 көрсетеді өзектілігі. Егер сұрау сүзгі мәтінмәнінде орындалса, _score мәні әрқашан 1-ге тең болады, бұл сүзгіге толық сәйкестікті білдіреді.

Анализаторлар

Анализаторлар бастапқы мәтінді таңбалауыштар жинағына түрлендіру үшін қажет.
Анализаторлар біреуден тұрады Токенизатор және бірнеше қосымша Токенсүзгілері. Токенизатордың алдында бірнеше болуы мүмкін CharFilters. Токенизаторлар бастапқы жолды бос орындар және тыныс белгілері сияқты таңбалауыштарға бөледі. TokenFilter таңбалауыштарды өзгерте алады, жаңаларын жояды немесе қоса алады, мысалы, сөздің тек түбірін қалдыра алады, предлогтарды алып тастайды, синонимдерді қоса алады. CharFilter - бүкіл бастапқы жолды өзгертеді, мысалы, html тегтерін кесіп тастайды.

ES бірнеше бар стандартты анализаторлар. Мысалы, анализатор Ресей.

Пайдаланайық API стандартты және орыс анализаторлары «Котенка туралы күлкілі әңгімелер» жолын қалай өзгертетінін көрейік:

# используем анализатор 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
  } ]
}

Стандартты анализатор жолды бос орындарға бөліп, барлығын кіші әріпке түрлендірді, орысша анализатор маңызды емес сөздерді алып тастап, оны кіші әріпке түрлендірді және сөздердің өзегін қалдырды.

Орыс анализаторы қандай Tokenizer, TokenFilters, CharFilters пайдаланатынын көрейік:

{
  "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 отсутствуют */
    }
  }
}

html тегтерін қиып алатын орыс тіліне негізделген анализаторымызды сипаттап көрейік. Оны әдепкі деп атаймыз, өйткені әдепкі бойынша осы атаумен анализатор пайдаланылады.

{
  "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 тегтері бастапқы жолдан жойылады, содан кейін токенизатор стандарты оны таңбалауыштарға бөледі, нәтижесінде алынған таңбалауыштар кіші әріптерге ауысады, елеусіз сөздер жойылады, ал қалған таңбалауыштар сөздің өзегі болып қалады.

Индекс құру

Жоғарыда біз әдепкі анализаторды сипаттадық. Ол барлық жол өрістеріне қолданылады. Біздің хабарламада тегтер массиві бар, сондықтан тегтер анализатормен де өңделеді. Өйткені Біз жазбаларды тегке дәл сәйкестік бойынша іздейміз, содан кейін тегтер өрісі үшін талдауды өшіруіміз керек.

Тегтер өрісінің талдауы өшірілген анализаторы және салыстыруы бар блог2 индексін жасайық:

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"
        }
      }
    }
  }
}'

Осы индекске (blog3) бірдей 2 жазбаны қосамыз. Мен бұл процесті өткізбеймін, себебі... бұл блог индексіне құжаттарды қосуға ұқсас.

Өрнек қолдауымен толық мәтінді іздеу

Сұраныстың басқа түрін қарастырайық:

# найдем документы, в которых встречается слово 'истории'
# 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

Егер сізді ұқсас мақалалар-сабақтар қызықтырса, жаңа мақалалар туралы идеяларыңыз болса немесе ынтымақтастық туралы ұсыныстарыңыз болса, мен жеке хабарламада немесе электрондық пошта арқылы хабарлама алуға қуаныштымын. [электрондық пошта қорғалған].

Ақпарат көзі: www.habr.com