Rails + MySQL で日本語全文検索

Railsアプリケーションに日本語による全文検索機能を付け加えるため、私は2つの選択肢を検討しました:

結論としては、後者を採用しました。

Tritonnを採用すれば、アプリケーション側の負担は非常に少なくなります。しかし、MySQL 5.0 ベースで Tritonn をビルドしなければなりません。ビルド作業自体はそんなに面倒ではないのですが、私が使用する予定のサーバマシンでは既に MySQL 5.1 が動いていたので、もうひとつ MySQL インスタンスを動かすことになります。メモリリソースのことが心配になりました。

他方、後者の方法では、既存の MySQL 5.1 をそのまま使用できます。Rails 側で MeCab を使うのは簡単ですし、アプリケーション側でいろんな工夫をする余地があるのも嬉しいところです。例えば、助詞や助動詞を検索対象から省くとか。

実際のRailsアプリケーションでは、次のようなコードを書きました。

まず、analyzed_texts テーブルを作ります。MyISAM で作らないと FULLTEXT インデックスを設定できない点に注意。


class CreateAnalyzedTexts < ActiveRecord::Migration
def self.up
create_table :analyzed_texts, :options => 'ENGINE=MyISAM' do |t|
t.integer :article_id, :null => false
t.text :words

t.timestamps
end

add_index :analyzed_texts, :article_id
execute "CREATE FULLTEXT INDEX analyzed_texts_words ON analyzed_texts(words)"
end

def self.down
drop_table :analyzed_texts
end
end

次に、AnalyzedText モデルを作ります。


require 'MeCab'

class AnalyzedText < ActiveRecord::Base
belongs_to :article

def analyze(text)
self.words = MeCab::Tagger.new("-Owakati").parse(text)
self.save!
end
end

検索対象となる Article モデルはこんな感じ。


require 'MeCab'

class Article < ActiveRecord::Base
has_one :analyzed_text
after_save :analyze_body

protected
def analyze_body
if body.present?
at = analyzed_text || AnalyzedText.new(:article_id => id)
at.analyze(body)
end
end

def self.search(text)
words = MeCab::Tagger.new("-Owakati").parse(text)
words = connection.quote(words)
includes(:analyzed_text).
where("MATCH (analyzed_texts.words) AGAINST (#{words} IN BOOLEAN MODE)").
order("MATCH (analyzed_texts.words) AGAINST (#{words} IN BOOLEAN MODE) DESC").
end
end

なお、my.cnf の [mysqld] セクションに次の記述を追加して、MySQL を再起動するのを忘れないこと。


ft_min_word_len=1


参考資料: