6
5

More than 5 years have passed since last update.

searchkickのデフォルト設定では、日本語のもしかして検索ができない件を調査する

Last updated at Posted at 2016-08-31

image

レストランデータを使った検索でsearchkickを使ったときにRestaurant.reindexのあとに、この検索がうまくいかなかったので、どのようにしたらうまくいくか調べてみます。

だいたいsenseで検証しています。

候補データ自体はどのようになっているか

image

フィールドはどうなっているか

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"
            },
...

実際にトークンがどのように分割されているのか確かめる

日本語である「すきやばしこじろう」で行った場合

image

Tokenizerはstandardを使っているので日本語の場合は、1文字ずつ分かれています。Token Filterではshingleのmax_shingle_sizeが5であるため、最大でも5文字でしかトークンとして保存されていません。

英語である「Summer Sonic」で行った場合

image

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方式で展開
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5