ボクココ

個人開発に関するテックブログ

Elasticsearch Rails の調査レポート ~実運用へ向けて~

ども、@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").recordsrecordsを付与すると変換してくれる。これにより、 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 な環境で運用していくとまた違う問題などが発生すると思う。適宜本ブログで報告していきたい。