Ruby on Rails で ElasticSearch と Chewyを活用して全文検索を実装しています。
Chewyとは?
Chewy is an ODM (Object Document Mapper), built on top of the official Elasticsearch client.
https://github.com/toptal/chewy
検索した際にN+1問題がBulletで検知されました。
objects = ParentModelIndex.query(match_query).load.objects
objects.each do |obj|
obj.child # ここの行で
end
N+1が検出されます。
GET /...
USE eager loading detected
ParentModel => [:child_model]
Add to your query: .includes([:child_model])
Call stack
ChatGPTの解決策
これをどのように対応すべきか?ChatGPTの提案は以下の通りでした。
ids = ParentModelIndex.query(match_query).load.map(&:id)
objects = ParentModel.where(id: ids).includes(:child_model)
納得いきますかこのコード? Chewy::Search::Response.objects
の処理は以下の通りです。
def objects
@objects ||= begin
objects = @loader.load(hits)
if @paginator
@paginator.call(objects)
else
objects
end
end
end
ChatGPTの処理は劣化処理になっています。
Chewyのコードを読み解く
そこでソースコードを読み解き増すとChewy::Search::Request.load
の定義は以下の通りです。
# Stores ORM/ODM objects loading options. Options
# might be define per-index or be global, depends on the adapter
# loading implementation. Also, there are 2 loading options to select
# or exclude indexes from loading: `only` and `except` respectively.
# Options are updated on further method calls.
#
# @example
# PlaceIndex.load(only: 'city').load(scope: -> { active })
# @see Chewy::Search::Loader
# @see Chewy::Search::Response#objects
# @see Chewy::Search::Scrolling#scroll_objects
# @param options [Hash] adapter-specific loading options
def load(options = nil)
modify(:load) { update!(options) }
end
結論
以下のようにload
の際にscope
を指定してincludes
してください。
ParentModelIndex.query(match_query).load(scope: -> {includes([:child_model])})