Elasticsearch は、Json REST API を備えた検索エンジンで、Lucene を使用し、Java で書かれています。 このエンジンのすべての利点の説明は、次の場所で参照できます。
文書データベース内の複雑な検索には、同様のエンジンが使用されます。 たとえば、言語の形態を考慮した検索や地理座標による検索などです。
この記事では、ブログ記事のインデックス作成を例にESの基礎についてお話します。 ドキュメントをフィルター、並べ替え、検索する方法を説明します。
OSに依存しないように、ESへのリクエストはすべてCURLを使用して行います。 Google Chrome 用のプラグインもあります。
テキストにはドキュメントやその他のソースへのリンクが含まれています。 最後に、ドキュメントに簡単にアクセスできるリンクがあります。 馴染みのない用語の定義は、次の場所にあります。
インストール
これを行うには、まず Java が必要です。 開発者
ES ディストリビューションは次の場所から入手できます。 bin/elasticsearch
. こちらも利用可能
インストールして起動したら、機能を確認してみましょう。
# для удобства запомним адрес в переменную
#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 によるドキュメントの取得:
# извлечем документ с 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
出力には、並べ替えの実行に使用される整数の配列が含まれます。 それらの。 日付は整数に変換されました。 並べ替えの詳細については、次を参照してください。
フィルターとクエリ
バージョン 2 以降の ES では、フィルターとクエリが区別されません。
クエリ コンテキストは、クエリが _score を生成し、キャッシュされないという点でフィルター コンテキストとは異なります。 _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" : [ "котята" ]
}
} ]
}
}
全文検索
XNUMX つのドキュメントには、コンテンツ フィールドに次の内容が含まれています。
<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
見せる
アナライザー
アナライザーは XNUMX つで構成されます
ESにはいくつかあります
使ってみよう
# используем анализатор 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 タグがソース文字列から削除され、次にトークナイザー標準によってトークンが分割され、結果のトークンが小文字に移動され、重要でない単語が削除され、残りのトークンは単語の語幹のままになります。
インデックスの作成
上記では、デフォルトのアナライザーについて説明しました。 すべての文字列フィールドに適用されます。 投稿にはタグの配列が含まれているため、タグもアナライザーによって処理されます。 なぜならタグと完全に一致する投稿を探しているので、タグ フィールドの分析を無効にする必要があります。
アナライザーとマッピングを使用して、タグ フィールドの分析が無効になっているインデックス blog2 を作成しましょう。
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"
]
}
}
}'
なぜならロシア語ステミングを備えたアナライザーを使用しているため、このリクエストは「history」という単語のみを含むすべてのドキュメントを返します。
リクエストには次のような特殊文字が含まれる場合があります。
""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
同様の記事レッスンに興味がある場合、新しい記事のアイデアがある場合、または協力の提案がある場合は、個人メッセージまたは電子メールでメッセージを喜んで受け取ります。 [メール保護].
出所: habr.com