Elasticsearch негиздери

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

Окшош кыймылдаткычтар документ базасында татаал издөө үчүн колдонулат. Мисалы, тилдин морфологиясын эске алуу менен издөө же гео координаттар боюнча издөө.

Бул макалада мен блог билдирүүлөрүн индекстөө мисалында ES негиздери жөнүндө сүйлөшөм. Документтерди кантип чыпкалоо, сорттоо жана издөөнү көрсөтөм.

Операциялык системадан көз каранды болбоо үчүн, мен CURL аркылуу ESге бардык сурамдарды жасайм. деп аталган 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 кысылган баштапкы документти сактайт. Эгерде бизге бүт баштапкы документ эмес, 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 жаратат жана кэштелбейт. _Упай эмне экенин кийинчерээк көрсөтөм.

Датасы боюнча чыпкалоо

Биз өтүнүчтү колдонобуз кырка чыпка контекстинде:

# получим посты, опубликованные 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ге барабар болот, бул чыпкага толук дал келет.

талдагычтары

талдагычтары баштапкы текстти белгилердин жыйындысына айландыруу үчүн керек.
Анализаторлор бирден турат Токенизатор жана бир нече кошумча TokenFilters. Токенизатордун алдында бир нече болушу мүмкүн 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"
        }
      }
    }
  }
}'

Ушул эле 3 постту ушул индекске (blog2) кошолу. Мен бул процессти өткөрүп жиберем, анткени... бул блог индексине документтерди кошууга окшош.

Экспресс колдоосу менен толук текстти издөө

Сурамдын дагы бир түрүн карап көрөлү:

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

Source: www.habr.com