Elasticsearchの基本

Elasticsearchは、Luceneを使用し、Javaで記述されたJSON REST APIを備えた検索エンジンです。 このエンジンのすべての利点の説明は、 公式Webサイトで入手できます。 テキストでは、ElasticsearchをESと呼びます。







このようなエンジンは、ドキュメントのデータベースの複雑な検索で使用されます。 たとえば、言語の形態を考慮した検索、または地理座標による検索。







この記事では、ブログ投稿のインデックス作成の例でESの基本について説明します。 ドキュメントをフィルター、ソート、検索する方法を示します。







オペレーティングシステムに依存しないように、CURLを使用してESにすべてのリクエストを行います。 senseと呼ばれるgoogle chromeプラグインもあります。







テキストには、ドキュメントおよびその他のソースへのリンクが含まれています。 最後に、ドキュメントにすばやくアクセスするためのリンクがあります。 なじみのない用語の定義は用語集にあります。







ESのインストール



これを行うには、最初にJavaが必要です。 開発者 、Java 8 update 20またはJava 7 update 55よりも新しいバージョンのJavaをインストールすることをお勧めします。







ESディストリビューションは、開発者のサイトで入手できます。 アーカイブを解凍した後、 bin/elasticsearch



を実行する必要があります。 aptおよびyumのパッケージも利用できます。 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は、ブログインデックスと投稿タイプを自動的に作成しました。 条件付きアナロジーを描くことができます。インデックスはデータベースであり、タイプはこのデータベースのテーブルです。 各タイプには、 マッピングとリレーショナル表という独自のスキームがあります。 ドキュメントのインデックス作成時にマッピングが自動的に生成されます。







 #  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



は、クエリに一致するドキュメントのtotal



示します。 出力のsort



は、ソートの実行に使用される整数の配列が含まれます。 つまり 日付は整数に変換されました。 ドキュメントでソートの詳細をご覧ください。







フィルターとクエリ



バージョン2のESは、フィルターとクエリを区別せず、代わりにコンテキスト概念が導入されています。

要求が_scoreを生成し、キャッシュされないという点で、要求コンテキストはフィルターコンテキストと異なります。 _scoreとは何ですか?







日付フィルタリング



フィルターコンテキストで範囲クエリを使用します。







 #  ,  1    curl -XGET "$ES_URL/blog/post/_search?pretty" -d' { "filter": { "range": { "published_at": { "gte": "2014-09-01" } } } }'
      
      





タグフィルタリング



クエリという用語を使用して、指定された単語を含む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" : [ "" ] } } ] } }
      
      





全文検索



3つのドキュメントのコンテンツフィールドには次のものが含まれています。









一致クエリを使用して、指定された単語を含む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



フィールドは関連性を示します。 要求がフィルターコンテキストで実行される場合、_scoreの値は常に1になり、フィルターに完全に準拠することを意味します。







アナライザー



ソーステキストをトークンのセットに変換するには、 パーサーが必要です。

アナライザーは、1つのTokenizerといくつかのオプションのTokenFiltersで構成されます。 Tokenizerの前には、いくつかのCharFilterがあります。 トークナイザーは、ソース文字列を、スペースや句読点などによってトークンに分割します。 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タグが元の文字列から削除され、次にトークナイザー標準トークンに分割され、受信したトークンは小文字になり、重要でない単語は削除され、残りの単語トークンは単語の基礎のままになります。







インデックス作成



上記では、デフォルトのアナライザーについて説明しました。 すべての文字列フィールドに適用されます。 投稿にはそれぞれタグの配列が含まれており、タグもアナライザーによって処理されます。 なぜなら タグに完全に一致する投稿を探している場合は、タグフィールドの分析を無効にする必要があります。







タグフィールドの分析が無効になっているアナライザーとマッピングを使用して、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" } } } } }'
      
      





このインデックス(blog2)に同じ3つの投稿を追加します。 私はこのプロセスを省略します これは、ブログインデックスにドキュメントを追加することに似ています。







式をサポートする全文検索



別の種類のクエリに慣れてみましょう。







 #  ,     '' # 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



そのような記事が興味深い場合、新しい記事のアイデアや協力の提案がある場合は、PMまたはメールm.kuzmin+habr@darkleaf.ruでお知らせいたします。








All Articles