PostgreSQLは標準機能では日本語のテキストを全文検索することはできません。PostgreSQLにPGroonga(ぴーじーるんが)という拡張機能を導入することで日本語のテキストを全文検索できるようになります。しかもPGroongaは高速です。Wikipedia日本語版のテキスト(約185万件・平均約4KB)から約2万件ヒットするような全文検索をしても0.2秒かかりません。
PostgreSQLと全文検索エンジンサーバーを組み合わせて日本語全文検索を実現することもできますが、管理するサーバーが増える・SQL以外に全文検索エンジンサーバーのことを覚える必要があるなど開発・運用時のコストが高くなります。PostgreSQLだけで完結できた方が開発時も運用時も楽になります。
この記事ではRuby on Railsで作ったアプリケーションからPGroongaを使って日本語全文検索機能を実現する方法を説明します。実際にドキュメント検索システムを開発する手順を示すことで説明します。ここではCentOS 7を用いますが、他の環境でも同様の手順で実現できます。
PostgreSQLとPGroongaのインストール
まずPostgreSQLとPGroongaをインストールします。CentOS 7以外の場合にどうすればよいかはPGroongaのインストールドキュメントを参照してください。
% sudo -H rpm -ivh http://yum.postgresql.org/9.4/redhat/rhel-$(rpm -qf --queryformat="%{VERSION}" /etc/redhat-release)-$(rpm -qf --queryformat="%{ARCH}" /etc/redhat-release)/pgdg-centos94-9.4-1.noarch.rpm
% sudo -H rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
% sudo -H yum install -y postgresql94-pgroonga
% sudo -H /usr/pgsql-9.4/bin/postgresql94-setup initdb
% sudo -H systemctl enable postgresql-9.4
% sudo -H systemctl start postgresql-9.4
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
で雛形を作ります。PostgreSQLのクライアントライブラリー(pg gem)をビルドするためにpg_config
コマンドがインストールされているパスをPATH
環境変数に指定しています。指定しなくてもpg_config
コマンドが見つかるなら指定する必要はありません。
% sudo -H yum install -y postgresql94-devel
% PATH=/usr/pgsql-9.4/bin:$PATH rails new document_search --database=postgresql
% cd document_search
therubyracer gemを有効にします。
# gem 'therubyracer', platforms: :ruby
% sudo -H yum install -y gcc-c++
% bundle install
PostgreSQLに接続するユーザーを作成します。スーパーユーザー権限をつけているのはCREATE EXTENSION
を実行するにはスーパーユーザー権限が必要だからです。CREATE EXTENSION
はPGroongaを有効にするときに使います。
% sudo -u postgres -H createuser ${USER} --superuser
ユーザーを作成しPostgreSQLにデータベースを作成できるようになったのでデータベースを作成します。
% bin/rake db:create
ここまでは(ほぼ)PGroongaと関係ない手順です。アプリケーションがPostgreSQLを使う場合にはよくある手順です。
ここからはPGroongaを使う場合に特有の手順になります。
まず、データベースでPGroongaを使えるようにします。
マイグレーションファイルを作成します。
% bin/rails generate migration EnablePGroonga
invoke active_record
create db/migrate/20151109091221_enable_p_groonga.rb
db/migrate/20151109091221_enable_p_groonga.rb
を次のような内容にします。search_path
を設定しているのはPGroongaが提供している演算子をpg_catalog
にある組み込みの演算子よりも優先的に使うためです。
class EnablePGroonga < ActiveRecord::Migration
def change
reversible do |r|
current_database = select_value("SELECT current_database()")
r.up do
enable_extension("pgroonga")
execute("ALTER DATABASE #{current_database} " +
"SET search_path = '$user',public,pgroonga,pg_catalog;")
end
r.down do
execute("ALTER DATABASE #{current_database} RESET search_path;")
disable_extensioin("pgroonga")
end
end
end
end
これでPGroongaを使う準備が整いました。
続いて検索対象のドキュメントを格納するテーブルを作成します。
% bin/rails generate scaffold document title:text content:text
% bin/rake db:migrate
全文検索用のインデックスを作成します。
まずマイグレーションファイルを作成します。
% bin/rails generate migration AddFullTextSearchIndexToDocuments
invoke active_record
create db/migrate/20151109092724_add_full_text_search_index_to_documents.rb
db/migrate/20151109092724_add_full_text_search_index_to_documents.rb
は次のような内容にします。ここでusing: "pgroonga"
を指定してインデックスを追加することがポイントです。
class AddFullTextSearchIndexToDocuments < ActiveRecord::Migration
def change
add_index(:documents, :content, using: "pgroonga")
end
end
このマイグレーションファイルを反映します。
% bin/rake db:migrate
PostgreSQL側の準備はできたのでアプリケーション側に全文検索機能を実装します。
モデルに全文検索用のスコープを定義します。PGroongaでは@@
演算子で全文検索をします。この演算子を使うと「キーワード1 OR キーワード2
」のようにORを使ったクエリーを指定できます。
class Document < ActiveRecord::Base
scope :full_text_search, -> (query) {
where("content @@ ?", 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が効いていることがわかります。
まとめ
PostgreSQLとPGroonga(ぴーじーるんが)を使ってRuby on Railsアプリケーションで日本語全文検索機能を実現する方法を説明しました。
ポイントは次の通りです。
-
enable_extension("pgroonga")
-
ALTER DATABASE SET search_path
-
add_index(using: "pgroonga")
-
where("content @@ ?", query)
開発時・運用時のことを考えてPostgreSQLベースの日本語全文検索機能の実現を検討してみてはいかがでしょうか。
おしらせ
今月の29日(11月29日)にPGroongaのイベントがあります。PGroongaに興味がでてきた方は↓のイベントページからお申し込みください。発表内容から有益な情報を得られますし、開発者に直接質問することもできます。
Groonga Meatup 2015 - Groonga | Doorkeeper