背景
プロダクトに全文検索機能が必要となりElasticSearchを使って開発することに、かといってElasticSearchの実装・運用経験がない私。
そこで最近Amazon Elasticsearch Serviceがリリースされたので導入してみました。
Amazon Elasticsearch Service
Amazon Elasticsearch Service は、AWS クラウドで Elasticsearch を簡単にデプロイ、
操作、スケーリングできるようにするマネージドサービスです。
(https://aws.amazon.com/jp/elasticsearch-service より引用)
スタートアップで働く私にとってできる限りサービス開発に時間を割きたく、インフラストラクチャへの注意を可能な限り少なくしたいです。
その点においてAmazon Elasticsearch Serviceの導入は悪くないと考えました。
Amazon Elasticsearch Serviceセットアップ
ドメイン
ドメインを指定します。
注意として例えばcoubic
というドメインを指定すると下記のようにsearch
がprefixされたendpointになります。
search-coubic-xxxxxxxxxxxxxxxxx.aws-region.es.amazonaws.com
##クラスター
ElasticSearchは、1台だけで検索サーバーとして動作します。また、大量のデータを処理したり耐障害性を実現するために複数のサーバー間で協調して動作することもできます。
ElasticSearchではこれらのサーバー群のことをクラスタ、群をなす1台1台のサーバーのことをノードと呼びます。
サービス要求に応じてクラスター等の設定をします。
料金イメージについては (http://calculator.s3.amazonaws.com/index.html?lng=ja_JP) よりシミュレーションすることが可能です。
※必ずしも正確な料金であることを私は保証できません。
##アクセスポリシー
サービス構成の都合に合わせてアクセスポリシーを設定します。
Application
目的
商品をタイトル、詳細から全文検索できるようにします。
Gem
RailsからAmazon Elasticsearch Serviceを簡単に使えるGemをインストールします。
gem 'elasticsearch-model'
gem 'elasticsearch-dsl'
elasticsearch-modelはActiveRecordからElasticSearchを簡単に使える機能を提供
elasticsearch-dslはrubyからElasticSearchの検索クエリを簡単に書けるDSLを提供
接続設定
Amazon Elasticsearch Serviceへの接続を指定します。
Elasticsearch::Model.client =
Elasticsearch::Client.new hosts: [
{host: 'your-aws-es-endpoint',
port: '80'}]
私はここでport指定をせずに少しハマりました。
インデックスから検索まで流れ
アプリケーションへ検索を実装する前に、ElasticSearchがどのように動作するか初めに大体の流れを理解します。
- インデキシング
- ElasticSearchに送られてきたデータを処理し、インデックスに保存する。
- アナライズ
- インデキシングの時にデータの内容を解析し分割しインデックスに書き込む。
- 検索
- クエリの条件を満たすドキュメントを探す。
- アナライザ
- 検索時にクエリの操作を指定する。
インデックス作成から検索まで順に見ていきます。
インデックス名決定
インデックスはElasticSearchがデータを保存する場所です。RDBMSへ例えるとテーブルに相当するものと言えるでしょうか。
今回はモデル単位でインデックスを作成する想定です。
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
index_name [model_name.collection, Rails.env].join('_')
end
end
##スキーママッピング
スキーママッピングはインデックス構造を定義するために使用します。
インデックスは複数のタイプを含めることができますが、ここでは1つのタイプを定義しています。
次の属性を持つタイプであるCourseをスキーママッピングします。
- name :string
- description :text
module CourseSearchable
extend ActiveSupport::Concern
included do
include Searchable
settings index: {
analysis: {
tokenizer: {
kuromoji_tokenizer: {
type: 'kuromoji_tokenizer',
mode: 'search'
}
},
filter: {
pos_filter: {
type: 'kuromoji_part_of_speech',
stoptags: %w(助詞-格助詞-一般 助詞-終助詞),
},
greek_lowercase_filter: {
type: 'lowercase',
language: 'greek',
},
},
char_filter: {
custom_mapping: {
type: 'mapping',
mappings: ['カ => カ', 'ガ=>ガ']
}
},
analyzer: {
kuromoji_analyzer: {
type: 'custom',
tokenizer: 'kuromoji_tokenizer',
filter: %w(kuromoji_baseform pos_filter greek_lowercase_filter cjk_width),
char_filter: %w(custom_mapping)
}
}
}
}
mappings dynamic: 'false' do
indexes :name, analyzer: 'kuromoji_analyzer', index: 'analyzed'
indexes :description, analyzer: 'kuromoji_analyzer', index: 'analyzed'
end
def as_indexed_json(options = {})
attributes
.symbolize_keys
.slice(:name, :description)
end
end
end
実際にはCourseモデルにCourseSearchableモジュールをincludeしています。
class Course < ActiveRecord::Base
include CourseSearchable
end
インデックスの作成
rails consoleからAmazon Elasticsearch Serviceへインデックスを作成します。
Course.__elasticsearch__.create_index! force: true
Amazon Elasticsearch Serviceのダッシュボードからインデックスが作成されたことを確認できると思います。
データの投入
rails consoleからAmazon Elasticsearch Serviceへ前商品データを投入します。
Course.__elasticsearch__.import
検索
入力されたキーワードから全文検索を行うクエリを組み立て、検索を実行します。
module CourseSearchable
extend ActiveSupport::Concern
.....省略
def self.search(query, size)
search_definition = Elasticsearch::DSL::Search.search {
size size
query do
multi_match do
query keyword
fields %w(name description)
end
end
}
__elasticsearch__.search(search_definition)
end
end
end
実際の検索ではElasticSearchの検索結果からRDBMSへ問い合わせを行い、検索結果に肉付けをしています。
courses = Course.search(keyword, SEARCH_LIMIT)
results = Course.where course_id: courses.records.to_a.map(&:id)
ドキュメント更新
Courseモデルが作成・更新された時にElasticSearchへドキュメントの更新を伝えます。
UXを考慮して、ActiveJobを使って非同期で処理しています。
class IndexerJob < ActiveJob::Base
include Elasticsearch::Model
queue_as :default
def perform(course)
course.__elasticsearch__.update_document
end
end
終わり
シンプルですが全文検索ができました。
弊社サービスよりデモが確認できます。
https://popcorn.coubic.com/search?category=13&location=1&q=%E8%82%A9%E3%81%93%E3%82%8A