Elasticsearch 101
Elasticsearchを使う機会があったのでBasicsをメモ。
Elasticsearchとは?
ElasticsearchはApacheプロジェクトの一つである検索用サーバでApache Luceneの上に構築されている。RESTfulなインターフェイスで分散型の構成や全文検索が可能であり、基本的に取り入れるデータの定義、検索クエリ共にJSONで記述される。
仕組み?
ESの導入や基本的な構成はこのあたりのページを見たらだいたいわかると思う。
http://code46.hatenablog.com/entry/2014/01/21/115620 http://engineer.wantedly.com/2014/02/25/elasticsearch-at-wantedly-1.html
基本的にESで重要なのは以下の2点である。
- 取り込むデータの定義
- 検索の詳細の定義
これらの中でも特にキモとなりそうな点を以下にメモ。
使用方法?
以下では実際にElasticsearchを動かしながらIndexの設定と簡単なクエリの発行を行う。 なお、今回使用した全てのJSONはここにある。
取り込むデータの定義
Elasticsearchのインデックスは基本的にはスキーマレスではあるが、取り込むデータのフィールドの定義やdelimiterをmappingにて定義しなけれな成らない。 以下ではデータ整形とフィールド定義の2つのパートに分けて説明する。
データの整形フォーマットの定義
まずここでは取り込むデータの整形フォーマットの定義を行う。以下のJSONはmapping
の前半部分である。
整形方法の定義はanalyzer
に記述され、このanalyzer
は tokenizer
と filter
の組み合わせによって定義される。
tokenizer
は文字列の分割方法を定義し、以下の例にあるngram_token
は最小2文字、最大3文字のngramを利用する。
filter
は分割後の文字列の整形処理を定義するのだが今回は利用しなかった。
analyzer
はtokenizer
とfilter
を組み合わせて複数定義することが可能であり、それぞれのfieldで異なるanalyzer
を利用することが可能である。
"settings": { "analysis": { "analyzer": { "ngram_analyzer": { "tokenizer": "ngram_token" } }, "tokenizer": { "ngram_token": { "type": "nGram", "min_gram": "2", "max_gram": "3", "token_chars": [ "letter", "digit" ] } } } }
フィールドの定義
以下のmapping
の後半ではインデックスとして登録される各フィールドのデータ型、使用するanalyzer
を定義する。
今回作成するmapping
の"sushiya"では、それぞれのフィールドは寿司屋の名前、住所、緯度経度情報を保持する。
ここでは、nameはスペースごとに文字列を分割するデフォルトanalyzer
である”whitespace”を使用し、addressに対しては上記にて定義したngam_analyzer
を使用した。
"mappings": { "sushiya": { "properties": { "name": { "type": "string", "analyzer": "whitespace" }, "address": { "type": "string", "analyzer": "ngram_analyzer" }, "location": { "type": "geo_point", "store": "yes" } } } }
以上にて定義した寿司屋情報保持用のmapping
をsample_mapping_sushi.jsonとして保存し、以下のコマンドにてrestrantインデックスとして定義する。
curl -XPOST localhost:9200/restaurant -d @sample_mapping_sushi.json
データの登録
今回はお気に入りの寿司屋を以下のように定義し登録する。_bulk
はまとめてデータを登録するためのパラメータである。
curl -XPOST localhost:9200/_bulk -d ' { "index": { "_index": "restaurant", "_type": "sushiya", "_id": "1" } } { "id": "1", "name": "鮨水谷", "location" : [35.66836,139.761106], "address" :"東京都中央区銀座8-7-7" } {"index": { "_index": "restaurant", "_type": "sushiya", "_id": "2" } } { "id": "2", "name": "すきばやし次郎", "location": [35.672614,139.764037], "address" : "東京都中央区銀座4-2-15" } { "index": { "_index": "restaurant", "_type": "sushiya", "_id": "3" } } { "id": "3", "name": "久兵衛", "location": [35.672614,139.764037], "address" : "東京都中央区銀座8-7-6" } { "index": { "_index": "restaurant", "_type": "sushiya", "_id": "4" } } { "id": "4", "name": "かねさか", "location": [35.667989,139.762643], "address" :"東京都中央区銀座8-10-3"} { "index": { "_index": "restaurant", "_type": "sushiya", "_id": "5" } } { "id": "5", "name": "鮨 なかむら", "location": [35.662347,139.729366], "address" : "港区六本木7丁目17−16" } '
ここでデータの定義について軽く説明すると以下の通りである。
{ "index": <-インデックスの情報について { "_index": "restaurant", <-どのインデックスを利用するか "_type": "sushiya", <-どのマッピングをを利用するか "_id": "1" <-keyとなる任意の文字列 } } { "id": "1", <-先に定義したidと一致するように定義 "name": "鮨水谷", <-ミシュラン三ツ星(高い) "location" : [35.66836,139.761106], <-geo_point typeの形式に沿った緯度経度情報 "address" :"東京都中央区銀座8-7-7" <-銀座は高値の象徴 }
検索の詳細の定義
Elasticsearchの検索はquery
とfilter
によって行い, 抑えるべき点は
query
はscoreに影響する。filter
はscoreへの影響はないがキャッシングの有無の選択が可能
という点である。詳しくは公式のリファレンスを追って欲しい。
クエリの定義
ここではまず、簡潔なクエリの定義を行う。以下のクエリは鮨という単語を全てのフィールドに対して検索している。
{ "query" : { "simple_query_string" : { "query": "鮨", "fields": ["_all"], "default_operator": "and" } } } "default_operator": "or" }
そしてその返り値が以下の通りである。店名に"鮨”をもつ水谷となかむらが含まれている。
"took" : 4, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 2, "max_score" : 0.067124054, "hits" : [ { "_index" : "restaurant", "_type" : "sushiya", "_id" : "1", "_score" : 0.067124054, "_source":{ "id": "1", "name": "鮨水谷", "location" : [35.66836,139.761106], "address" :"東京都中央区銀座8-7-7" } }, { "_index" : "restaurant", "_type" : "sushiya", "_id" : "5", "_score" : 0.067124054, "_source":{ "id": "5", "name": "鮨 なかむら", "location": [35.662347,139.729366], "address" : "港区六本木7丁目17−16" } } ] } }
Scoreの定義
さらに、Elasticsearchでは検索スコアの重み付けがクエリ内で可能である。
見ての通り、以下のクエリでは店名に”鮨”が入っていればscoreを10倍し、場所が一定範囲内(六本木駅から2km)の場合はスコアが100倍される。
また他にもアクセスカウントによるsortの定義や、自身で定義した計算式によるscoreの計算も可能であり、非常に柔軟なクエリを記述する事ができる。
{ "query" : { "function_score" : { "query" : { "simple_query_string" : { "query": "鮨 銀座 六本木", "fields": ["_all"], "default_operator": "or" } }, "score_mode": "multiply", "boost_mode": "multiply", "functions" : [ { "filter" : { "term" : { "name" : "鮨"}}, "boost_factor" : 10 }, { "filter" : { "geo_distance" : { "distance" : "2km", "location" : [35.66, 139.73] }}, "boost_factor" : 100 } ] } } }
"sort" : [{ "access_count" : {"order" : "desc", "missing" : "_last"}}]
"script_score" : { "script" : "doc[\"access_count\"].value * 100" }
おわりに
以上に上げたとおり、Elasticsearchはデータの取り込みもクエリの組み立ても非常に容易に出来る。
最近良く見られるのがElasticsearchとKibanaの組み合わせであり、例えばfulentdで得られたログ情報をElasticsearchに取り込むことでログの整形を容易にし、Kibanaで可視化することでログの分析が簡単に行える。
また、クローリング用のプラグインや、日本語の形態素解析用のプラグインが既に出回っており、Elasticsearchを取り巻く環境は確実にに発展している。 Elasticsearchは簡単に設定ができ強力であるため新しく検索エンジンが必要になった場合には使用をおすすめしたいフレームワークである。
高速スケーラブル検索エンジン ElasticSearch Server (アスキー書籍)
- 作者: Rafal Kuc (lにストローク符号、cにアクサン・テギュ付く),Marek Rogozinski (nにアクサン・テギュ付く)
- 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
- 発売日: 2014/03/25
- メディア: Kindle版
- この商品を含むブログ (2件) を見る
Apache Lucene 入門 ~Java・オープンソース・全文検索システムの構築
- 作者: 関口宏司
- 出版社/メーカー: 技術評論社
- 発売日: 2006/05/17
- メディア: 大型本
- 購入: 5人 クリック: 156回
- この商品を含むブログ (31件) を見る