Help us understand the problem. What is going on with this article?

padrino+PostgreSQLで作ったサイトを高速全文検索対応にする

概要

Dockerコンテナ上で動かしているpadrino+postgresqlなサービスに全文検索機能をつけたいとおもった。
ここではpgroongaを用いる。
タイトルに高速といれたのは、同種の機能拡張pg_bigmより早いみたいなので、groongaを選定したことによる。
が、どちらかというと手軽さをとった。

準備

Dockerfileで指定しているpostgresイメージを、pgroongaに変更した。
PostgreSQL向けにpgroongaというgroonga用エクステンションがあることは知っていたが、pgroongaイメージはPostgreSQLを内包しており、差し替えるだけで全文検索対応したPostgreSQLとして利用できる。

docker/pgroonga/Dockerfile
FROM groonga/pgroonga:2.2.6-debian-12
#FROM postgres:12

docker-compose.yamlはdb部分だけは↓みたいな感じ。Windows上でつかっているので、別途 docker volume create --name pgdata1している。

docker-compose.yml
version: '2'

services:
  db:
    build: docker/pgroonga
    container_name: pgoongadb
    ports:
      - "15432:5432"
    environment:
      - POSTGRES_USER=sa
      - POSTGRES_PASSWORD=sa
      - POSTGRES_DB=pgdb
    volumes:
      - pgdata1:/var/lib/postgresql/data
volumes:
  pgdata1:
    external: true

みてのとおり、Dockerfileを準備するまでもなく postgresをgroonga/pgroongaにしただけである。
(あとでmecab-ipadic-neologdを使うつもりでDockerfileを分けている)

こまったこと

データベースは上記で準備できた。
しかし、肝心の全文検索むけインデックスをpadrinoでいい感じに作成する方法がわからなかった。
ActiveRecordのadd_indexではチュートリアルにあるようなwith~的な記述が出来ない。
事例を検索してみたが見つからない。ActiveGroongaというのもあったが、それはちょっと違う。

groongaのコミュニティに相談したところ、須藤さんよりredmineでの事例を教えていただく。
またpadrinoでもこうすれば動くよ、というアドバイスもいただき、試した。

解決編

padrinoのプロジェクト内にある、libフォルダに下記のようなスクリプトを配置。

lib/migrate_plugin_add_index.rb
module Migration
  module PostgreSQLAdapterOptionable
    def add_index_options(table_name, column_name, with: nil, **options)
      result = super(table_name, column_name, **options)
      result[3] += " WITH (#{with})" if with
      result
    end
  end
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapterOptionable)
end

上記プラグインを追加した状態で、padrino g migration add_fulltext_indexとかなんとかマイグレーションを作成。

005_add_fulltext_index.rb
class AddFulltextIndex < ActiveRecord::Migration[5.2]
  def self.up
    add_index(:contacts, :name, using: 'pgroonga', with: "tokenizer='TokenMecab(\"use_reading\", true)'")
    add_index(:contacts, :description, using: 'pgroonga', with: "tokenizer='TokenMecab'")
    add_index(:contacts, :tel, using: 'pgroonga', with: "normalizer='NormalizerNFKC100(\"unify_hyphen_and_prolonged_sound_mark\", true)',tokenizer='TokenNgram(\"loose_symbol\", true,\"loose_blank\", true)'")
  end

  def self.down
  end
end

rake db:migrateすると無事、以下のようなSQLが発行され、create indexされた。

 add_index(:contacts, :name, {:using=>"pgroonga", :with=>"tokenizer = 'TokenMecab(\"use_reading\", true)'"})
DEBUG - (47.6ms) CREATE INDEX "index_contacts_on_name" ON "contacts" USING pgroonga ("name") WITH (tokenizer = 'TokenMecab("use_reading", true)')

試しにテストデータをつっこんでDataGripのクエリコンソールで、「東京都生まれヒップホップ育ち」みたいなデータを持つカラムに対し検索を実行。正しくおもったとおり動作。

select * from contacts where description &@ '京都' -- > なし
select * from contacts where description &@ '東京' -- > 検索結果 ○

まとめ

  • Docker上で動かしているなら、イメージをpostgresからpgroongaにするだけで全文検索対応にできる
  • プラグイン用ライブラリを1ファイル置いておけばpadrinoでもadd_indexするmigrationでインデックス作成できる

調べてわからなかったらコミュニティに聞いてみるのも手。場当たり対応でなくrails(ActiveRecord)側にもリクエストしておいたほうがいいかもという話をもらって、添削してもらいつつでissueも出してみた。

paichi81@github
永遠の初心者
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away