サイトへ戻る

Rails + MySQLで全文検索

システムで検索を用意する時には色々選択肢があります。

Solr, MySQL(Like, FullText, Senna...), Elasticsearch Service

一長一短がありますが、MySQLのFulltextが選択されるのは最近ではそんなに多くない気がします。

見聞きしたり携わった案件では、大体Solrなど検索用のミドルウェア入れるか、LIKE検索で済ますかのケースが多いですね。

予算がある程度以上あり、ユーザ数が10万人を超える程度のサービスであれば、0.1%のユーザでも100人いるので、入力のゆらぎ対応することが必要です。

逆に予算もコストもない場合、関わるエンジニアが他の選択肢を知らなかったりして、LIKE検索になります。
(ダサいダサいと聞きますが、ユーザにストレスなく使われているならそれは別に良いのでは?と思っています。)

ですので、日本語環境だとMySQLのFulltextが選択されることはあまりないかなと考えていました。

今回のシステム(スタンプ先生)だと、スタンプ名とアーティスト名に個性的な名付けが多かったので、分かち書きよりngramの方がスコアが良かったです。そもそもアーティスト名で検索した時に一般的な単語ではないとして検索漏れが起こる事はNGですし、そのために別ロジックを書くのも「うーむ…」となってしまいました。

妥協でLIKE検索にしたら割と無視できない検索速度(SQL単体で80msec前後)になってしまい、レコード数はこれからも増えるし困ったな…ということで、MySQLのFulltextを選択しました。

具体的にはこんな感じです

-- my.cnf

innodb_ft_min_token_size=1

-- MySQL (InnoDB)

ALTER TABLE stamps ADD search_key TEXT COLLATE utf8_general_ci;
ALTER TABLE stamps ADD FULLTEXT INDEX ngram_search_key(search_key) WITH PARSER ngram;

-- Rails Modelで検索用データ設定
self.search_key = "#{self.title} #{self.description} #{self.author.name}"

-- Rails Controllerで検索

Stamp.where("MATCH(search_key) AGAINST(?)", params[:search_key])

-- Rails config/environment.rbに追記

con = ActiveRecord::Base.connection
if con.select_one("SHOW VARIABLES like 'innodb_ft_min_token_size'")["Value"] != "1"
raise "Set MySQL Variable 'innodb_ft_min_token_size=1' in my.cnf "
end

割と良かったです

SQL単体だと他にも色々条件ありますが0.2msec前後に落ち着けました。これなら問題ありません。

やはり1アーキテクチャ・1テーブルで完結すると楽ですね。ActiveRecordが絡んだGemをそのまま使えるのでありがたいです。

1サービス1サーバじゃないこともあるのでmy.cnfだけは流石にgit管理出来ませんが、それ以外はgitの管理下におけます。my.cnf設定漏れたらRails起動時にこけるので設定漏れも起こりません。

これに入れました。

http://stampsensei.com