MySQLは先日リリースされたMySQL 5.7から標準機能で日本語のテキストを全文検索できるようになりました。逆に言うと、現在広く使われているMySQL 5.6以前では日本語のテキストを全文検索できません。MySQLにMroonga(むるんが)というストレージエンジンを導入することで日本語のテキストを全文検索できるようになります。しかもMroongaは高速です。MySQL 5.7で導入された日本語全文検索機能よりも高速です。
MySQLと全文検索エンジンサーバーを組み合わせて日本語全文検索を実現することもできますが、管理するサーバーが増える・SQL以外に全文検索エンジンサーバーのことを覚える必要があるなど開発・運用時のコストが高くなります。MySQLだけで完結できた方が開発時も運用時も楽になります。
この記事ではRuby on Railsで作ったアプリケーションからMroongaを使って日本語全文検索機能を実現する方法を説明します。実際にドキュメント検索システムを開発する手順を示すことで説明します。ここではCentOS 7を用いますが、他の環境でも同様の手順で実現できます。
MySQLとMroongaのインストール
まずMySQLとMroongaをインストールします。CentOS 7以外の場合にどうすればよいかはMroongaのインストールドキュメントを参照してください。
% sudo -H yum install -y http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
% sudo -H yum install -y http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
% sudo -H yum install -y mysql-community-server
% sudo -H systemctl start mysqld
% sudo -H yum install -y mysql-community-mroonga
Rubyのインストール
CentOS 7にはRuby 2.0のパッケージがありますが、Ruby on Rails 4.2.4はRuby 2.2が必要なのでrbenvとruby-buildでRuby 2.2をインストールします。
% sudo -H yum install -y git
% git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
% git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
% echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
% echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
% exec ${SHELL} --login
% sudo -H yum install -y gcc make patch openssl-devel readline-devel zlib-devel
% rbenv install 2.2.3
% rbenv global 2.2.3
Ruby on Railsのインストール
Ruby on Railsをインストールします。
% gem install rails
ドキュメント検索システムの開発
いよいよ日本語全文検索機能を持ったドキュメント検索システムを開発します。
まずはrails new
で雛形を作ります。
% sudo -H yum install -y mysql-community-devel
% rails new document_search --database=mysql
% cd document_search
Active Record 4.2.4ではmysql2 gemは0.3系でなければいけないのでGemfileでバージョンを指定します。
Gemfile
:
@@ -4,7 +4,7 @@ source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.4'
# Use mysql as the database for Active Record
-gem 'mysql2'
+gem 'mysql2', '~> 0.3.20'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
バージョンを更新します。
% bundle install
therubyracer gemを有効にします。
# gem 'therubyracer', platforms: :ruby
% sudo -H yum install -y gcc-c++
% bundle install
データベースを作成します。
% bin/rake db:create
ここまでは(ほぼ)Mroongaと関係ない手順です。アプリケーションがMySQLを使う場合にはよくある手順です。
ここからはMroongaを使う場合に特有の手順になります。
まず検索対象のドキュメントを格納するテーブルを作成します。
% bin/rails generate scaffold document title:text content:text
rake db:migration
する前にマイグレーションファイルを編集してストレージエンジンをMroongaに変更します。
@@ -1,6 +1,6 @@
class CreateDocuments < ActiveRecord::Migration
def change
- create_table :documents do |t|
+ create_table :documents, options: "ENGINE=Mroonga" do |t|
t.text :title
t.text :content
このマイグレーションファイルを反映します。
% bin/rake db:migrate
全文検索用のインデックスを作成します。
まずマイグレーションファイルを作成します。
% bin/rails generate migration AddFullTextSearchIndexToDocuments
invoke active_record
create db/migrate/20151109143515_add_full_text_search_index_to_documents.rb
db/migrate/20151109143515_add_full_text_search_index_to_documents.rb
は次のような内容にします。ここでtype: :fulltext
を指定してインデックスを追加することがポイントです。
class AddFullTextSearchIndexToDocuments < ActiveRecord::Migration
def change
add_index :documents, :content, type: :fulltext
end
end
このマイグレーションファイルを反映します。
% bin/rake db:migrate
MySQL側の準備はできたのでアプリケーション側に全文検索機能を実装します。
モデルに全文検索用のスコープを定義します。MySQLで全文検索を実行するときはMATCH(...) AGAINST('...')
を使います。Web検索エンジンのように「キーワード1 OR キーワード2
」とORを使ったクエリーを指定できるMATCH(...) AGAINST('*D+ ...' IN BOOLEAN MODE')
がオススメです。
class Document < ActiveRecord::Base
scope :full_text_search, -> (query) {
where("MATCH(content) AGAINST(? IN BOOLEAN MODE)", "*D+ #{query}")
}
end
ビューにヒット件数表示機能と検索フォームをつけます。検索フォームではquery
というパラメーターに検索クエリーを指定することにします。
app/views/documents/index.html.erb
:
@@ -2,6 +2,13 @@
<h1>Listing Documents</h1>
+<p><%= @documents.count %> records</p>
+
+<%= form_tag(documents_path, method: "get") do %>
+ <%= search_field_tag "query", params["query"] %>
+ <%= submit_tag "Search" %>
+<% end %>
+
<table>
<thead>
<tr>
@@ -5,6 +5,10 @@ class DocumentsController < ApplicationController
# GET /documents.json
def index
@documents = Document.all
+ query = params[:query]
+ if query.present?
+ @documents = @documents.full_text_search(query)
+ end
end
# GET /documents/1
これで日本語全文検索機能は実現できました。簡単ですね。
動作を確認するためにQiitaから検索対象のドキュメントを取得するRakeタスクを作ります。
lib/tasks/data.rake
:
require "open-uri"
require "json"
namespace :data do
namespace :load do
desc "Load data from Qiita"
task :qiita => :environment do
tag = "groonga"
url = "https://qiita.com/api/v2/items?page=1&per_page=100&query=tag:#{tag}"
open(url) do |entries_json|
entries = JSON.parse(entries_json.read)
entries.each do |entry|
Document.create(title: entry["title"],
content: entry["body"])
end
end
end
end
end
実行して検索対象のドキュメントを作成します。
% bin/rake data:load:qiita
http://localhost:3000/documents
にアクセスし、フォームに「オブジェクト」と日本語のクエリーを入力します。元のドキュメントは100件あり、「オブジェクト」で絞り込んで16件になっています。日本語で全文検索できていますね。
次のようにOR検索もできます。「オブジェクト」単体で検索したときの16件よりも件数が増えているのでORが効いていることがわかります。
まとめ
MySQLとMroonga(むるんが)を使ってRuby on Railsアプリケーションで日本語全文検索機能を実現する方法を説明しました。
ポイントは次の通りです。
-
create_table(options: "ENGINE=Mroonga")
-
add_index(type: :fulltext)
-
where("MATCH(content) AGAINST(? IN BOOLEAN MODE)", "*D+ #{query}")
開発時・運用時のことを考えてMySQLベースの日本語全文検索機能の実現を検討してみてはいかがでしょうか。
おしらせ
今月の29日(11月29日)にMroongaのイベントがあります。Mroongaに興味がでてきた方は↓のイベントページからお申し込みください。発表内容から有益な情報を得られますし、開発者に直接質問することもできます。