【SDK 利用編】Rails API × Meilisearch SDK をつかってみる
この記事の続きです。
Rails
を利用して API の開発を行っています。
検索機能を Meilisearch
を用いて実装したいので、meilisearch-rails
gem という Rails
向けの Meilisearch SDK
を利用して User
インデックスの検索・ドキュメント作成を rails console
上で確認します。
環境構築
下記記事内で、テストデータを作成していますが、作成しなくても大丈夫です。
meilisearch-rails について
間違いなく公式が詳しいです。
ここでは私が個人的に注目したい部分を紹介します。
前提
こんなかんじのモデルを例にいろいろ書いていきます。
class User < ApplicationRecord
include MeiliSearch::Rails
# Meilisearch 上のプライマリーキー
meilisearch primary_key: :id
meilisearch do
# インデックス追加対象となるフィールド
attribute :id, :name, :name_ruby, :url
end
end
その他 Meilisearch
でのオプションに対応しています。
自動インデックス作成
RDB
側の users
テーブルの変更時に、自動的に Meilisearch
のインデックスと同期します。
# rails console で実行
# レコード作成
User.create(name: "namae", name_ruby: "ナマエ")
# -> RDB users テーブルにレコードが追加される
# -> Meilisearch にドキュメントが自動的に追加される
# レコード削除
User.find(1).destroy
# -> RDB users テーブル id = 1 のレコードを削除
# -> Meilisearch の対応するドキュメントも自動的に削除される
# レコード更新
user = User.find_by(id: 2)
user.update(name: "updated_name", name_ruby: "updated_name")
# -> RDB users テーブル id = 2 の属性を更新
# -> Meilisearch の対応するドキュメントも自動的に更新される
以前、Meilisearch
+ Lambda
で検索機能を実装していたころは DynamoDB Stream をつかったり、バッチ処理でドキュメント追加をぐるぐる回したりしてなかなか大変だった記憶があったので、この機能はフレームワークならではという感じでかなり便利だと感じました。
後から導入した際の indexing
既存のモデルに後から Meilisearch
を導入する際は reindex!
メソッドを使用することで Meilisearch
のインデックスを作成することができます。
また、再定義などを行った場合には、インデックスのドキュメントを一度全削除して再度インデックスを作成することもできます。
# rails console で実行
User.clear_index! # ドキュメントの全削除
User.reindex! # インデックス作成・ドキュメント追加
検索
meilisearch-rails
では、以下のように検索を行うことができます。
Meilisearch
API で検索を実行し、インデックスから得られた primary-key
の配列について RDB
上のテーブルに対して SELECT
をかけるというふうな流れで検索を実行しているみたいです。
つまり、以下のコードは、
hits = User.search('東京都')
# => [User, User ...]
以下の2つのステップを経て得られる結果ということになります。
curl \
-X POST 'http://meilisearch:7700/indexes/User/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer meili-master-key' \
--data-binary '{ "q": "東京都" }'
SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2, $3...)
このような仕様があるためかどうかは不明ですが、公式ではクライアントサイドからの API 利用を推奨しています。
確かにあいまい検索のハードルを越えることはできそうですが、RDB
への問い合わせを行う時点でその分実行時間は増えるので Meilisearch
の速度を活かしきれないような気がします。
とはいえ、ActiveRecord
の仕組みを活用することで他の API に倣った実装ができることや、キーを隠蔽することができたり、Meilisearch
側で余分なデータを持たなくてよくなるのはメリットといえそうです[1]。
一応 ms_raw_search
を使えば Meilisearch
API を叩いた結果の生データのみを取得することができますが、それをするくらいなら Ruby
用の SDK を使うか、クライアント側で頑張った方がいいのかなと。
互換性
meilisearch-rails
のメソッドは search
, index
などのメソッドを提供します。
すでにモデル側で同名のメソッドが定義されている場合、再定義はされることはありません。
メソッドのプレフィックスに ms_
を指定して実行することもできます。
hits = User.ms_search('東京都')
# => [User, User ...]
SDK を試してみる
事前準備
事前準備として、データを用意します。
今回は前回の記事でも使用した架空の User
データを使用します。
はじめるまえに
前回の記事で環境構築を行い、テストデータまで作成していた場合は、一度きれいにインデックスごと削除することをおすすめします。
削除方法
curl \
-X DELETE 'http://localhost:7700/indexes/{インデックス名}' \
-H 'Authorization: Bearer meili-master-key'
# コマンドプロンプト用
curl -X DELETE "http://localhost:7700/indexes/{インデックス名}" -H "Authorization: Bearer meili-master-key"
モデル作成
Rails
側でモデルを作成します。
rails g model user
自動生成された db/migrate/yyyymmddxxxxxx_create_user.rb
と models/user.rb
をそれぞれ記述し、db:migrate
します。
db/migrate/yyyymmddxxxxxx_create_user.rb
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :user_system_id, :null => false, comment: "ユーザーID"
t.string :name, :null => false, comment: "名前"
t.string :name_ruby, comment: "名前(フリガナ)"
t.integer :age, comment: "年齢"
t.string :pref, comment: "都道府県"
t.integer :pref_code, comment: "都道府県コード"
t.string :tel, comment: "電話番号"
t.string :url, comment: "URL"
t.timestamps
end
end
end
models/user.rb
class User < ApplicationRecord
include MeiliSearch::Rails
meilisearch primary_key: :id
meilisearch do
attribute :id, :name, :name_ruby, :url
end
end
rails db:migrate
テストデータ作成(RDB)
db:seed
でテストデータを作成します。
手元にサンプルデータ用の json
があれば seeds.rb
を以下のように書き換えて db:seed
でテストデータを生成することができます(参考記事: Qiita Railsでjsonファイルのデータをdbに入れる)。
ちなみにレコード数は1000件くらいがちょうどいいと思います。前回の記事で作成したデータを10万件そのままぶん回したら当然のように2時間くらいかかりました。
seeds.rb
user_json = ActiveSupport::JSON.decode(File.read(
Rails.root.join("db", "sample.json")
))
user_json.each do |d|
User.create(
uid: d["user_system_id"],
name: d["name"],
name_ruby: d["name_ruby"],
age: d["age"],
pref: d["pref"],
pref_code: d["pref_code"],
tel: d["tel"],
url: d["url"]
)
end
rails db:seed
meilisearch-rails の導入
meilisearch-rails
gem を導入します。
Gemfile
に以下を追記して bundle install
します。
gem "meilisearch-rails", "~> 0.10.2"
bundle install
Meilisearch 用の設定
config/initializers/meilisearch.rb
を作成するか以下のコマンドを実行して Meilisearch
用の設定ファイルを作成します。
rails meilisearch:install
以下のように書き換えます。
MeiliSearch::Rails.configuration = {
meilisearch_url: ENV.fetch('MEILISEARCH_HOST', 'http://meilisearch:7700'),
meilisearch_api_key: ENV.fetch('MEILISEARCH_API_KEY', {設定した API キー})
}
ドキュメントでは、MEILISEARCH_HOST
に localhost
を指定していますが、 Docker
を利用している場合は、コンテナ名を指定する必要があるので注意が必要です。
Meilisearch にインデックスを作成する
rails console
上で最初のインデックス作成を行っていきます。
rails c
> User.reindex!
プレビュー画面で、RDB
側のテーブルのレコード数と Meilisearch
側のドキュメントのレコード数が一致しているか確認してください。
疎通確認
rails console
上で、検索とインデックスの自動作成がうまくいくか、試してみます。
検索
> User.ms_search("ヒガシタニ")
# => User Load (41.9ms) SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2,...)
インデックスの自動作成
> User.create(user_system_id: "tekiteki", name: "適当な名前", name_ruby: "tekitonanamae")
# => TRANSACTION (0.3ms) BEGIN
# => User Create (4.1ms) INSERT INTO "users" ("user_system_id", "name", ...)
# => TRANSACTION (3.9ms) COMMIT
> User.ms_search("tekitonanamae")
# => User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1...
さいごに
API 開発なら、クライアントから Meilisearch
の API を SDK を利用して呼び出した方が速度も担保できるのではと思ったが、Rails
の serializer
や ActiveRecord
の仕組みを活用できるので他の API に倣った実装ができること、キーを隠蔽できたりするのはメリットといえそう。
そうした Rails
の各種機能が使えること + あいまい検索の機能向上 + 検索ロジック・ルールをバックエンドで持てること、などのメリットに対して、 SQL
での問い合わせによるパフォーマンス低下をどのくらい許容できるのかは考えた方がいいと思う。
一方で、meilisearch-rails
を導入することによる、RDB
-> Meilisearch
のオートインデックス機能はかなりのメリットがあると感じた。
Meilisearch
に全乗っかりするならクライアントでもバックエンドでも API ごりごり使っていくのもいいかも。
次回はこの続きで、検索 API とレコード作成 API を作っていきます。
参考
Meilisearch 関連
meilisearch-rails 関連
rails db:seed
-
一応クライアント側でキーを暗号化するなどの方法も一応あるらしい。そもそもとにかく隠蔽しないといけないものではないのかもしれない(Google MAP の API KEY も確かそんな感じだった気がする) ↩︎
Discussion