Searchkick経由でのkuromojiが機能しない

初めてここで質問させていただきます :bowing_man:

現在、RailsのGemであるSearchkick経由でkuromojiプラグインを使っているのですが、Kibanaコンソールではちゃんと形態素解析され目的の検索結果がでるのに、localhost:3000で実際に検索するとヒットしません。

前提・実現したいこと

各カテゴリーで良質な情報を提供するサイトのデータベースを作っています。Railsで「searchkick」と「kuromoji」 を使って、投稿(Post)の検索機能を実装しているところです。

以下のER図のように、PostとCategoryは多対多で、かつCategoryはAncestryを使って多階層化されています。

例えばnameが「世界名所100選」であるPostが、Category「絶景」をリレーションとして持っているとき、「日本の絶景」と検索してもヒットするようにしたいです。

発生している問題・エラーメッセージ

kibanaコンソールでは以下のようにGETすると上手くヒットするのですが、実際の(Rails s で立ち上げたlocalhost:3000にて)検索窓に「日本の絶景」と打ち込んでも何もヒットしません。

GET /posts_development/_search
{
  "query": {
    "multi_match": {
      "fields": [ "name", "description", "category_name"],
      "query": "日本の絶景",
      "analyzer": "searchkick_index"
    }
  }
}

該当のソースコード

インデックスの設定

GET /posts_development?pretty

返り値↓ 

{
  "posts_development_20200709141330421" : {
    "aliases" : {
      "posts_development" : { }
    },
    "mappings" : {
      "dynamic_templates" : [
        {
          "string_template" : {
            "match" : "*",
            "match_mapping_type" : "string",
            "mapping" : {
              "fields" : {
                "analyzed" : {
                  "analyzer" : "searchkick_index",
                  "index" : true,
                  "type" : "text"
                }
              },
              "ignore_above" : 30000,
              "type" : "keyword"
            }
          }
        }
      ],
      "properties" : {
        "category_name" : {
          "type" : "keyword",
          "fields" : {
            "analyzed" : {
              "type" : "text",
              "analyzer" : "searchkick_index"
            }
          },
          "ignore_above" : 30000
        },
        "description" : {
          "type" : "keyword",
          "fields" : {
            "analyzed" : {
              "type" : "text",
              "analyzer" : "searchkick_index"
            }
          },
          "ignore_above" : 30000
        },
        "name" : {
          "type" : "keyword",
          "fields" : {
            "analyzed" : {
              "type" : "text",
              "analyzer" : "searchkick_index"
            }
          },
          "ignore_above" : 30000
        }
      }
    },
    "settings" : {
      "index" : {
        "max_ngram_diff" : "49",
        "number_of_shards" : "1",
        "provided_name" : "posts_development_20200709141330421",
        "max_shingle_diff" : "4",
        "creation_date" : "1594271610438",
        "analysis" : {
          "filter" : {
            "searchkick_suggest_shingle" : {
              "max_shingle_size" : "5",
              "type" : "shingle"
            },
            "searchkick_edge_ngram" : {
              "type" : "edge_ngram",
              "min_gram" : "1",
              "max_gram" : "50"
            },
            "searchkick_index_shingle" : {
              "token_separator" : "",
              "type" : "shingle"
            },
            "searchkick_search_shingle" : {
              "token_separator" : "",
              "output_unigrams_if_no_shingles" : "true",
              "output_unigrams" : "false",
              "type" : "shingle"
            },
            "searchkick_ngram" : {
              "type" : "ngram",
              "min_gram" : "1",
              "max_gram" : "50"
            }
          },
          "analyzer" : {
            "searchkick_word_start_index" : {
              "filter" : [
                "lowercase",
                "asciifolding",
                "searchkick_edge_ngram"
              ],
              "type" : "custom",
              "tokenizer" : "standard"
            },
            "searchkick_keyword" : {
              "filter" : [
                "lowercase"
              ],
              "type" : "custom",
              "tokenizer" : "keyword"
            },
            "searchkick_text_end_index" : {
              "filter" : [
                "lowercase",
                "asciifolding",
                "reverse",
                "searchkick_edge_ngram",
                "reverse"
              ],
              "type" : "custom",
              "tokenizer" : "keyword"
            },
            "searchkick_search2" : {
              "type" : "kuromoji"
            },
            "searchkick_word_middle_index" : {
              "filter" : [
                "lowercase",
                "asciifolding",
                "searchkick_ngram"
              ],
              "type" : "custom",
              "tokenizer" : "standard"
            },
            "searchkick_search" : {
              "type" : "kuromoji"
            },
            "searchkick_text_start_index" : {
              "filter" : [
                "lowercase",
                "asciifolding",
                "searchkick_edge_ngram"
              ],
              "type" : "custom",
              "tokenizer" : "keyword"
            },
            "searchkick_word_end_index" : {
              "filter" : [
                "lowercase",
                "asciifolding",
                "reverse",
                "searchkick_edge_ngram",
                "reverse"
              ],
              "type" : "custom",
              "tokenizer" : "standard"
            },
            "searchkick_word_search" : {
              "filter" : [
                "lowercase",
                "asciifolding"
              ],
              "type" : "custom",
              "tokenizer" : "standard"
            },
            "searchkick_autocomplete_search" : {
              "filter" : [
                "lowercase",
                "asciifolding"
              ],
              "type" : "custom",
              "tokenizer" : "keyword"
            },
            "searchkick_suggest_index" : {
              "filter" : [
                "lowercase",
                "asciifolding",
                "searchkick_suggest_shingle"
              ],
              "type" : "custom",
              "tokenizer" : "standard"
            },
            "searchkick_text_middle_index" : {
              "filter" : [
                "lowercase",
                "asciifolding",
                "searchkick_ngram"
              ],
              "type" : "custom",
              "tokenizer" : "keyword"
            },
            "searchkick_index" : {
              "type" : "kuromoji"
            }
          },
          "char_filter" : {
            "ampersand" : {
              "type" : "mapping",
              "mappings" : [
                "&=> and "
              ]
            }
          }
        },
        "number_of_replicas" : "1",
        "uuid" : "irv_ZciHQuOZc_QndmsG1Q",
        "version" : {
          "created" : "7080099"
        }
      }
    }
  }
}

Railsのコード

class Post < ApplicationRecord
    has_many :post_category_relations
    has_many :categories, through: :post_category_relations

    searchkick language: "japanese"

    def search_data
        {
            name: name,
            description: description,
            category_name: categories.map(&:name)
        }
    end
end

上記でlanguage: "japanese"を付与してPost.reindexするだけで、analyzersearchkick_indexsearchkick_searchtypeがkuromojiに指定されるようです。

class Category < ApplicationRecord
    has_many :post_category_relations
    has_many :posts, through: :post_category_relations
    has_ancestry

    after_commit :reindex_post

    def reindex_post
        post.reindex
    end
end
  def index
    @posts = self.query
  end

  private
    def query
      if params[:q]
        Post.search params[:q], fields: [:name, :description, :category_name]
      else
        Post.all
      end
    end

試したこと

kibanaコンソールでの実験

GET /posts_development/_search
{
  "query": {
    "multi_match": {
      "fields": [ "name", "description", "category_name"],
      "query": "日本の絶景",
      "analyzer": "searchkick_index" // ←ここを削除するとヒットしない
    }
  }
}

"analyzer": "searchkick_index"を削除するとヒットしません。しかしfieldsのcategory_namecategory_name.analyzedとするとヒットします。

GET /posts_development/_analyze
{
  "text" : "日本の絶景"
}
→「日」「本」「の」「絶」「景」の5トークン
GET /posts_development/_analyze
{
  "analyzer": "searchkick_index",
  "text" : "日本の絶景"
}
→「日本」「絶景」の2トークン
GET /posts_development/_analyze
{
  "tokenizer": "kuromoji_tokenizer", 
  "text" : "日本の絶景"
}
→「日本」「の」「絶景」の3トークン

補足情報(FW/ツールのバージョンなど)

  • Ruby 2.7.1
  • Rails 6.0
  • elasticsearch-oss 7.8.0 (/usr/local/bin/elasticsearch)
  • kibana-oss 7.8.0 (/usr/local/bin/kibana)
  • kuromoji 7.8.0
  • searchkick 4.4.1
  • ancestry 3.0.7

teratailでも質問したのですが、ここを教えていただき質問させていただきました。1日近くググれども実験せども正直お手上げ状態で、お助けいただけますと幸いです🙇‍♀️

まったくSearchkickを存じ上げないので、的外れかもしれませんが・・・

Kibanaで検索できることが確認できているという状態なので、Searchkick経由でなぜ検索できないのか、デバッグログを出してみてうまくいくクエリと比較するのはどうでしょうか。

また、just ideaですが、queryingの箇所に以下のように記載があり、何もないとandと書いてあります。「日本の絶景」だと「日本 & 絶景」となり、「絶景」だけを含むカテゴリ名がヒットしないのでは?と。とりあえずorをつけてみるのはどうでしょう。

Product.search "fresh honey" # fresh AND honey
Product.search "fresh honey", operator: "or" # fresh OR honey

multi_matchの方は、デフォルトでoperatorがorなのでもしかしたら?と思った次第です。

1 Like

おおおおお、このような長い質問にご回答ありがとうございます😭神です、ほんと。

そしてorめちゃめちゃ怪しいですね!おそらくビンゴだと思います😅

私もログを見ようとしてさらに苦戦してたとこですが、searchkick側にdebugの設定があったとは…!

感謝感激です☺️

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.