ども、@kimihom です。
今回は Ruby on Rails で Elasticsearch を使う方法について調査したので報告しよう。
特に ActiveRecord と Elasticsearch をどう連携させるのか、そこら辺を詳しく書こうと思う。
elasticsearch-rails の利用
さて、 Rails で Elasitcsearch を利用する場合、上記の gem を利用することになるが、この gem は何者なのか自分の理解でまとめる。
- Elasticsearch での index, type の指定
- ActiveRecord 上のオブジェクトで Elasticsearch へ保存するフィールドを指定
- ActiveRecord(or Mongoid) でのデータを Elasticsearch に一括インポート
- ActiveRecord でオブジェクトを作成・更新・削除したタイミングでデータを同期
- Elasticsearch で検索
この Gem は3つに分かれるが、ActiveRecord を使う場合は、
gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
の2つの gem を利用する。elasticsearch-model が今回重要なコアとなる ActiveRecord の拡張だ。 elasticsearch-rails は インポートに便利な rake タスクの提供やログの出力の設定などが実装されている。
elasticsearch-model で検索する
さて、細かな設定方法はドキュメントを読めばいいのだが、特に導入において重要な点を以下に挙げる。
ActiveRecord と Elasticsearch の同期
どうやって ActiveRecord と Elasticsearch を同期させるか、は私自身、とても悩んでいたことだった。実際に elasticsearch-model を見てみると、幾つかのやり方があるように見える。まず一番シンプルなのは、 ActiveRecord の create, update, destroy のコールバックを用いて、そのタイミングで Elasticsearch にリクエストを送り、データを同期させる方法だ。
class Article < ActiveRecord::Base include Elasticsearch::Model after_commit on: [:create] do __elasticsearch__.index_document end after_commit on: [:update] do __elasticsearch__.update_document end after_commit on: [:destroy] do __elasticsearch__.delete_document end end
こうすれば基本的に ActiveRecord でデータが変われば Elasticsearch も変わるようになる。ただし、その分リクエストが発生するので、大量アクセスのあるサービスの場合は Resque などを利用する必要がある。
少なくともこれで ActiveRecord の CRUD時は Elasticsearch と同期させた運用が可能になる。しかし自分の環境の場合、 ActiveRecord を介さないで PostgreSQL に保存する場合があり、その時にどうやって Elasticsearch へデータを入れるかは未だ調査が必要な点だ。
Elasticsearch を ActiveRecord::Relation で扱う
例えば、 Article.search("hoge")
とした時に、 その戻りがActiveRecord::Relation
だと、where でのさらなる絞り込みやページング、グルーピングなどが可能になる。シンプルに Article.search("hoge").records
と records
を付与すると変換してくれる。これにより、 Elasticsearch ではデータが残っていたけども、ActiveRecord ではデータが削除されていたので、それは取得しない、ということが可能になる。Elasticsearch で ids を取得し、さらに ActiveRecord でその ids で検索を内部で動作させるからだ。これは2回リクエストを送るというやや冗長な感じになるが、データが必ず存在することを担保することが可能だ。
インデックスの作成とデータインポート
インデックスの作成はまずプログラムで実行させるようなコードを書く必要がある。
client = Elasticsearch::Client.new host: 'localhost:9200', logger: Logger client.indices.delete index: index_name rescue nil client.indices.create(index: index_name, body: { settings: settings.to_hash, mappings: mappings.to_hash })
こんな感じのを rake タスクなり model なりにうまい具合に記入し(上記コードじゃ動かないので変数にうまく値を入れる)、インデックスを作成しよう。そのあと、以下を lib/tasks/elasticsearch.rake
に記入しよう。
# lib/tasks/elasticsearch.rake require 'elasticsearch/rails/tasks/import'
これだけで bundle exec rake -D elasticsearch
が打てるようになり、このコマンドで ActiveRecord から Elasticsearch へインポートする方法が出来上がる。例えば Article に Elasticsearch の定義をしたならば、以下のような形でインポートが可能だ。
bundle exec rake environment elasticsearch:import:model CLASS='Article'
一括でサクッとインポートできるので大変便利なコマンドだ。
終わりに
私自身、まだ調査段階のレポートであるため、実際に Elasticsearch Rails な環境で運用していくとまた違う問題などが発生すると思う。適宜本ブログで報告していきたい。