Ruby on RailsでPostgreSQLとPGroongaを使って日本語全文検索を実現する方法 - 2015-11-09 - ククログ

ククログ

株式会社クリアコード > ククログ > Ruby on RailsでPostgreSQLとPGroongaを使って日本語全文検索を実現する方法

Ruby on RailsでPostgreSQLとPGroongaを使って日本語全文検索を実現する方法

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を有効にします。

Gemfileから次の行のコメントを外します

# gem 'therubyracer', platforms: :ruby

therubyracer gemをインストールします。

% 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が効いていることがわかります。

「オブジェクト OR API」で検索

まとめ

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

関連