レストランデータを使った検索でsearchkickを使ったときにRestaurant.reindexのあとに、この検索がうまくいかなかったので、どのようにしたらうまくいくか調べてみます。
だいたいsenseで検証しています。
候補データ自体はどのようになっているか
フィールドはどうなっているか
GET restaurants_development/restaurant/_mapping
結果は以下
...
"name": {
"type": "string",
"index": "not_analyzed",
"fields": {
"suggest": {
"type": "string",
"analyzer": "searchkick_suggest_index"
},
"word_start": {
"type": "string",
"analyzer": "searchkick_word_start_index"
}
},
"ignore_above": 256
},
...
searchkick_suggest_indexとsearchkick_word_start_indexはどうなっているかというと以下です。
GET restaurants_development/_settings
結果は以下
...
"searchkick_suggest_shingle": {
"max_shingle_size": "5",
"type": "shingle"
},
"searchkick_edge_ngram": {
"type": "edgeNGram",
"min_gram": "1",
"max_gram": "50"
},
...
"searchkick_word_start_index": {
"filter": [
"lowercase",
"asciifolding",
"searchkick_edge_ngram"
],
"type": "custom",
"tokenizer": "standard"
},
...
"searchkick_suggest_index": {
"filter": [
"lowercase",
"asciifolding",
"searchkick_suggest_shingle"
],
"type": "custom",
"tokenizer": "standard"
},
...
実際にトークンがどのように分割されているのか確かめる
日本語である「すきやばしこじろう」で行った場合
Tokenizerはstandardを使っているので日本語の場合は、1文字ずつ分かれています。Token Filterではshingleのmax_shingle_sizeが5であるため、最大でも5文字でしかトークンとして保存されていません。
英語である「Summer Sonic」で行った場合
Tokenizerはstandardでわけているので正しく単語にわかれていて意図どおりに動いています
実際searchkickはどのようにsuggestionを検索しているのか
pryなどで以下のように検索してみます。
Restaurant.search "すきやばしこじろう", suggest: true
すると以下のクエリを出していました。
GET restaurants_development/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"name.word_start": {
"query": "すきやばしこじろう",
"boost": 10,
"operator": "and",
"analyzer": "searchkick_word_search"
}
}
},
{
"match": {
"name.word_start": {
"query": "すきやばしこじろう",
"boost": 1,
"operator": "and",
"analyzer": "searchkick_word_search",
"fuzziness": 1,
"prefix_length": 0,
"max_expansions": 3,
"fuzzy_transpositions": true
}
}
}
]
}
},
"size": 1000,
"from": 0,
"suggest": {
"text": "すきやばしこじろう",
"name": {
"phrase": {
"field": "name.suggest"
}
}
},
"fields": []
}
日本語でsuggestを働かせるためにはどのようにしたらいいか
Elasticsearch キーワードサジェスト日本語のための設計を参考にして、searchkickのAdvanced Mappingと、Advanced Searchを利用するといけました。
ただ、Advanced Searchはそのままではオブジェクトがかえってこないで、Searchkick::Resultがかえってくるのでrecordsをみると、普通にsearchした場合と同じものがかえってきます。
Stations.reindexでindexを作った後にsense上ですが以下のようにクエリをだすと、望んだ結果を得ることができました。
# クエリ
GET stations_development/station/_search
{
"size": 3,
"query": {
"bool": {
"should": [{
"match": {
"name.autocomplete": {
"query": "ひがりなかの"
}
}
}, {
"match": {
"name.readingform": {
"query": "ひがりなかの",
"fuzziness": "AUTO",
"operator": "and"
}
}
}]
}
}
}
# 結果
{
"took": 6,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 2.3801262,
"hits": [
{
"_index": "stations_development_20160831152228025",
"_type": "station",
"_id": "2874",
"_score": 2.3801262,
"_source": {
"id": 2874,
"pref_id": 13,
"name": "東中野",
"name_kana": "ひがしなかの",
"property": "JR中央線",
"created_at": "2016-08-21T08:17:28.000+09:00",
"updated_at": "2016-08-21T08:17:28.000+09:00"
}
},
{
"_index": "stations_development_20160831152228025",
"_type": "station",
"_id": "5832",
"_score": 2.3102236,
"_source": {
"id": 5832,
"pref_id": 27,
"name": "針中野",
"name_kana": "はりなかの",
"property": "近鉄南大阪線",
"created_at": "2016-08-21T08:17:28.000+09:00",
"updated_at": "2016-08-21T08:17:28.000+09:00"
}
}
]
}
}
参考
Elasticsearch キーワードサジェスト日本語のための設計
- Completion Suggesterを使わないで実装する方法の紹介
- インデックス・データ設計
- サジェスト専用のインデックスを作成
- サジェスト対象のデータは検索履歴単位でデータをストアする
- 検索方法
- 1データ1ドキュメントとして入れる
- Terms Aggregationをつかって集計・ソートする
- データの保存・削除
- 対象が20GB超えそうなら日別や月別にインデックスを作成する
- ドキュメントにTTLを設定したりバッチで削除してもいい
- マッピング設計: 1つのフィールドに対して3つのタイプのフィールドを使う
- 集計表示用: 正規化のみ。Tokenは区切らない。
- 前方一致検索用: 正規化と、edge NGram方式で前方一致用のデータ作成
- 前方一致検索用(読み): 日本語でトークナイズして、ローマ字変換。そしてedge NGram方式で展開



