Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rails アプリの 5,000 件の N+1 問題と戦っている話

ANDPAD inc
October 27, 2023

Rails アプリの 5,000 件の N+1 問題と戦っている話

川原 万季 @makicamel
2023 年 10 月 27 日
Kaigi on Rails 2023
話者の勤める会社のとある歴史あるリポジトリでは CI で約 5,000 件の N+1 問題が検知されていました。 N+1 問題はパフォーマンス劣化の代表的な要因のひとつです。 Rails にはこれを解決する includes というメソッドがあり、これを警告する Bullet という gem があります。 Bullet の警告箇所に includes を差し込めばよさそうですが、5,000 件の手修正は億劫なので実行時に N+1 クエリ発行箇所に includes を差し込む gem を作りました。

しかし現実のアプリケーションは複雑で、単純に includes すればよいというものではありません。 例えば以下のコードは alias が重複しエラーになります。

Play.joins(:actors).includes(actors: :company).joins('INNER JOIN companies force index(primary) ON companies.id = actors.company_id').load

エラーにならなくとも不要な includes はパフォーマンス劣化を招きます。 こうした意図しない変更を避けて includes を差し込む必要があります。

本セッションでは作った gem の解説と、この gem による修正をリリースする話をします。

ANDPAD inc

October 27, 2023
Tweet

More Decks by ANDPAD inc

Other Decks in Technology

Transcript

  1.  plays = Play.limit(10) plays.each do |play| puts play.actors.map(&:name) end

     SELECT "plays".* FROM "plays" LIMIT 10 SELECT "actors".* FROM "actors" INNER JOIN "play_actors" ON "actors"."id" = "play_actors"."actor_id" WHERE "play_actors"."play_id" = 1 SELECT "actors".* FROM "actors" INNER JOIN "play_actors" ON "actors"."id" = "play_actors"."actor_id" WHERE "play_actors"."play_id" = 2 
 SELECT "actors".* FROM "actors" INNER JOIN "play_actors" ON "actors"."id" = "play_actors"."actor_id" WHERE "play_actors"."play_id" = 3 
 ... 10 + 1 ճͷΫΤϦ͕ൃߦ͞ΕΔ N+1 ໰୊ͱ͸
  2. ActiveRecord::QueryMethods#preload  plays = Play.preload(:actors).limit(10) plays.each do |play| puts play.actors.map(&:name)

    end  SELECT "plays".* FROM "plays" LIMIT 10 SELECT "play_actors".* FROM "play_actors" WHERE "play_actors"."play_id" IN (1, 2, 3, ...) SELECT "actors".* FROM "actors" WHERE "actors"."id" IN (1, 2, 3, ...) 3 ͭʹ෼͚ΒΕͨΫΤϦ͕ൃߦ͞ΕΔ
  3. ActiveRecord::QueryMethods#eager_load  plays = Play.eager_load(:actors).limit(10) plays.each do |play| puts play.actors.map(&:name)

    end  SELECT DISTINCT "plays"."id" FROM "plays" LEFT OUTER JOIN "play_actors" ON "play_actors"."play_id" = "plays"."id" LEFT OUTER JOIN "actors" ON "actors"."id" = "play_actors"."actor_id" LIMIT 10 SELECT "plays"."id" AS t0_r0, ... FROM "plays" LEFT OUTER JOIN "play_actors" ON "play_actors"."play_id" = "plays"."id" LEFT OUTER JOIN "actors" ON "actors"."id" = "play_actors"."actor_id" WHERE "plays"."id" IN (1, 2, 3, ...) LEFT OUTER JOIN ͨ͠ 
 ΫΤϦ͕ൃߦ͞ΕΔ
  4. ActiveRecord::QueryMethods#includes ActiveRecord::QueryMethods#includes
 https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes
 ೔ຊޠ͸ൃදऀ༁ Specify associations args to be eager

    loaded to prevent N + 1 queries. 
 A separate query is performed for each association, 
 unless a join is required by conditions. N + 1 ΫΤϦΛ๷͙ͨΊʹ args associations Λ eager_load ͢ΔΑ͏ࢦఆ͠·͢ɻ 
 ৚݅ʹΑͬͯ join ͕ඞཁͰͳ͚Ε͹ɺ 
 ֤ association ͝ͱʹݸผͷΫΤϦ͕࣮ߦ͞Ε·͢ɻ
  5. ActiveRecord::QueryMethods#includes ActiveRecordͷjoinsͱpreloadͱincludesͱeager_loadͷҧ͍ / @k0kubun
 https://qiita.com/k0kubun/items/80c5a5494f53bb88dc58 •includes ͨ͠ςʔϒϧͰ where ʹΑΔߜΓࠐΈΛߦ͍ͬͯΔ •includes

    ͨ͠ association ʹରͯ͠ joins ͔ references ΋ݺΜͰ͍Δ •೚ҙͷ association ʹରͯ͠ eager_load ΋ݺΜͰ͍Δ ͷ͏͍ͪͣΕ͔Λຬͨ͢৔߹ɺeager_load ͱಉ͡ڍಈ(LEFT JOIN)Λߦ͍ɺ 
 ͦ͏Ͱͳ͚Ε͹ preload ͱಉ͡ڍಈ(ΫΤϦΛ෼͚࣮ͯߦ)Λ͢Δɻ 
 ߜΓࠐΈ͕ඞཁͳ࣌ʹྫ֎Λ౤͛ͣ eager_load ʹfallback͢Δ preloadɻ
  6. fl yerhzm / bullet
 https://github.com/ fl yerhzm/bullet Bullet 🔫 ɹN+1

    ΍ෆཁͳ eager_loadingͳͲΛ 
 ɹϩά΍ը໘ʹදࣔͯ͠ڭ͑ͯ͘ΕΔ
  7.  # config/environments/test.rb Rails.application.configure do config.after_initialize do Bullet.enable = true

    Bullet.raise = true end end Bullet 🔫 ɹraise Λࢦఆ͢ΔͱΤϥʔΛى͜͢
  8.  BulletmarkRepairer 🩹 class PlaysController < ApplicationController def index @plays

    = Play.all end end  <% @plays.each do |play| %> <% play.actors.each do |actor| %> <%= actor.name %> <% end %> <% end %> N+1 ൃੜ
  9.  BulletmarkRepairer 🩹 class PlaysController < ApplicationController def index @plays

    = Play.all end end  <% @plays.each do |play| %> <% play.actors.each do |actor| %> <%= actor.name %> <% end %> <% end %> N+1 ൃੜ $ REPAIR=1 rspec
  10.  BulletmarkRepairer 🩹 class PlaysController < ApplicationController def index @plays

    = Play.all.includes([:actors]) end end  <% @plays.each do |play| %> <% play.actors.each do |actor| %> <%= actor.name %> <% end %> <% end %> ɹ࣮ߦ࣌ʹ autocorrect Ͱ 
 ɹincludes Λ௥Ճ͢Δ $ REPAIR=1 rspec
  11.  Case 1. class PlaysController < ApplicationController def index @plays

    = Play.all end end  <% @plays.each do |play| %> <% play.actors.each do |actor| %> <%= actor.name %> <% end %> <% end %> ɹϏϡʔͰ N+1 ͕ 
 ɹൃੜ͢Δέʔε
  12.  Case 1. class PlaysController < ApplicationController def index @plays

    = Play.all end end  <% @plays.includes(:actors).each do |play| %> <% play.actors.each do |actor| %> <%= actor.name %> <% end %> <% end %> ɹϏϡʔͰ 
 ɹincludes ͨ͘͠ͳ͍ 🙅
  13.  Case 1. class PlaysController < ApplicationController def index @plays

    = Play.all.includes(:actors) end end  <% @plays.each do |play| %> <% play.actors.each do |actor| %> <%= actor.name %> <% end %> <% end %> ɹίϯτϩʔϥͰ 
 ɹincludes ͍ͨ͠ 🙆
  14.  Case 2. class PlaysController < ApplicationController def index plays

    = Play.all.map do |play| { title: play.name, actors: play.actors.map(&:name) } end render json: { data: plays } end end ɹίϯτϩʔϥͰ 
 ɹൃੜ͢Δέʔε
  15.  Case 2. class PlaysController < ApplicationController def index plays

    = Play.all.includes(:actors).map do |play| { title: play.name, actors: play.actors.map(&:name) } end render json: { data: plays } end end ɹίϯτϩʔϥͰ 
 ɹ௚͢ͱΑͦ͞͏ 🙆
  16.  Case 3. class PlaysController < ApplicationController before_action :set_plays def

    index render json: { data: @plays.as_json } end private def set_plays @plays = Play.all end end ɹίʔϧόοΫͰ 
 ɹActiveRecord Λ 
 ɹ૊ΈཱͯΔέʔε
  17.  Case 3. class PlaysController < ApplicationController before_action :set_plays def

    index render json: { data: @plays.as_json } end private def set_plays @plays = Play.all.includes(:actors) end end ɹ͜ͷ৔߹͸ 
 ɹίʔϧόοΫͰ௚ͯ͠ 
 ɹΑͦ͞͏ 🙆
  18.  Case 4. class PlaysController < ApplicationController def index render

    json: { data: scope.as_json } end def show render json: { data: scope.find(params[:id]).as_json } end private def scope Play.where('name like ?', params[:name]) end end ɹڞ௨ͷείʔϓΛ 
 ɹ࢖͍ͬͯΔ৔߹
  19.  Case 4. class PlaysController < ApplicationController def index render

    json: { data: scope.as_json } end def show render json: { data: scope.find(params[:id]).as_json } end private def scope Play.where('name like ?', params[:name]) end end ɹΞΫγϣϯͰ 
 ɹ௚͢΂͖…ʁ🤔 ɹ͜ͷ͘Β͍ͳΒ 
 ɹείʔϓͰ௚ͯ͠΋ 
 ɹΑͦ͞͏͚ͩͲ…🤔 ɹshow Ͱ includes ͢Δ 
 ɹඞཁͳ͘ͳ͍ʁ🤔
  20.  Case 5. ɹίϯτϩʔϥ͔Β 
 ɹݺͼग़ͨ͠ 
 ɹϞσϧͷϝιουͰ 
 ɹN+1

    ͕ൃੜ͢Δέʔε class PlaysController < ApplicationController def index plays = Play.all render json: { data: plays.map(&:as_json_with_actors) } end end class Play < ApplicationRecord def as_json_with_actors as_json.merge(actors: actors.map(&:name)) end end
  21.  Case 5. class PlaysController < ApplicationController def index plays

    = Play.all.includes(:actors) render json: { data: plays.map(&:as_json_with_actors) } end end class Play < ApplicationRecord def as_json_with_actors as_json.merge(actors: actors.map(&:name)) end end ɹίϯτϩʔϥͰ 
 ɹ௚͢ͱΑͦ͞͏ 🙆
  22.  Case 6. ɹϞσϧͷίʔϧόοΫͰ 
 ɹΫΤϦ͠௚্ͨ͠Ͱ 
 ɹൃੜ͍ͯ͠Δέʔε class Play

    < ApplicationRecord after_save :notify_actors, if: :notification_required? private def notify_actors actors.where(...).each(&:notify!) end end
  23.  Case 6. class Play < ApplicationRecord after_save :notify_actors, if:

    :notification_required? private def notify_actors actors.where(...).includes(:actors).each(&:notify!) end end ɹϞσϧͰ 
 ɹ௚͢ͱΑͦ͞͏ʁ
  24.  Case 7. class TransitionPastPlaysService def initialize(border_date) @border_date = border_date

    end def execute plays = Play.where(created_at: ..@border_date) play_actors = plays.map do |play| { play_id: play.id, actor_id: play.actors.map(&:id) } end PastPlay.insert_all plays.map(&:as_json) PastPlayActor.insert_all play_actors plays.destroy_all end end ɹαʔϏε಺Ͱ 
 ɹPlay.where ্ͨ͠Ͱ 
 ɹൃੜ͢Δέʔε ※ ຊདྷ map(&:id) Ͱ͸ͳ͘ pluck(:id) ΍ ids Λ࢖͑͹ N+1 ͷӨڹ͸খ͘͞ͳΓ·͢
  25.  Case 7. class TransitionPastPlaysService def initialize(border_date) @border_date = border_date

    end def execute plays = Play.where(created_at: ..@border_date).includes(:actors) play_actors = plays.map do |play| { play_id: play.id, actor_id: play.actors.map(&:id) } end PastPlay.insert_all plays.map(&:as_json) PastPlayActor.insert_all play_actors plays.destroy_all end end ※ ຊདྷ map(&:id) Ͱ͸ͳ͘ pluck(:id) ΍ ids Λ࢖͑͹ N+1 ͷӨڹ͸খ͘͞ͳΓ·͢ ɹαʔϏε಺Ͱ 
 ɹ௚͔͢͠ͳ͍
  26. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end
  27. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end ɹBullet ͷϩά  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’
  28. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹελοΫτϨʔεʹ 
 ɹίϯτϩʔϥ͕ͳ͍
  29. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹमਖ਼͍ͨ͠ϑΝΠϧ͸ 
 ɹͬͪ͜ ɹελοΫτϨʔεʹ 
 ɹίϯτϩʔϥ͕ͳ͍
  30. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹϏϡʔͰ N+1 Λൃੜͤ͞Δ஋͸ 
 ɹଟ͘ͷ৔߹Πϯελϯεม਺ʹೖ͍ͬͯΔ
  31. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹN+1 ൃੜߦ͸Θ͔Δ
  32. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹର৅ͬΆ͍ 
 ɹΠϯελϯεม਺͕Θ͔Δʢצʣ
  33. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹURL ͕Θ͔Δ
  34. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹίϯτϩʔϥͱ 
 ɹΞΫγϣϯ͕Θ͔Δ
  35. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹΠϯελϯεม਺ʹ 
 ɹ୅ೖ͢Δͱ͜Ζʹ
  36. Ϗϡʔฤ   <% @plays.each do |play| %> <% play.actors.each

    do |actor| %> <%= actor.name %> <% end %> <% end %> class PlaysController < ApplicationController def index @plays = Play.all.includes(:actors) end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:2:in `block in _app_views_plays 
 _index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/ 
 index.html.erb:1:in `_app_views_plays 
 _index_html_erb__3206924200111145907_11500’ ɹincludes Λ଍͢✌
  37. Ϗϡʔฤ  <% @plays.each do |play| %> <% play.actors.each do

    |actor| %> <% if @favorite %> <%= actor.name %> <% end %> <% end %> <% end %> ର৅ͷΠϯελϯεม਺͸ 
 צͰ୳͍ͯ͠ΔͷͰ 
 ͜Μͳίʔυͩͱಈ͔ͳ͍ N+1 ൃੜՕॴ Πϯελϯεม਺Ά͍ 
 จࣈྻͦͷ 1ʢμ΢τʣ Πϯελϯεม਺Ά͍ 
 จࣈྻͦͷ 2ʢຊ෺ʣ
  38. Ϗϡʔฤ  <% Play.all.each do |play| %> <% play.actors.each do

    |actor| %> <%= actor.name %> <% end %> <% end %> Πϯελϯεม਺Λ 
 લఏͱ͍ͯ͠ΔͷͰ 
 ͜Μͳίʔυ΋ಈ͔ͳ͍
  39. ίϯτϩʔϥฤ  class PlaysController < ApplicationController def index plays =

    Play.all.map do |play| { title: play.name, actors: play.actors.map(&:name) } end render json: { data: plays } end end ɹ͜͜Ͱ includes ͢Δͱ 
 ɹΑͦ͞͏
  40. ίϯτϩʔϥฤ  class PlaysController < ApplicationController def index @plays =

    scope if params[:future] @plays = @plays.where(performed_at: Date.today..) end gon.as_json = @plays.as_json end def show @play = scope.find(params[:id]) end private def scope Actor.find(params[:actor_id]).plays end end ɹ͜͜Ͱ includes ͢Δͱ 
 ɹΑͦ͞͏
  41. ίϯτϩʔϥฤ  class PlaysController < ApplicationController before_action :set_plays def index

    render json: { data: @plays.as_json } end private def set_plays @plays = Play.all end end ɹ͜͜Ͱ includes ͢Δͱ 
 ɹΑͦ͞͏ʁ
  42. ίϯτϩʔϥฤ  class PlaysController < ApplicationController def index @plays =

    scope if params[:future] @plays = @plays.where(performed_at: Date.today..) end gon.as_json = @plays.as_json end def show @play = scope.find(params[:id]) end private def scope Actor.find(params[:actor_id]).plays end end ɹͻͱͭͷϝιου಺Ͱ 
 ɹෳ਺ճ୅ೖ͞Ε͍ͯΔ৔߹ 
 ɹͲͪΒ͕ਖ਼͍͔͠ 
 ɹػձతͳ൑ఆ͸ແཧ
  43. ίϯτϩʔϥฤ  class PlaysController < ApplicationController def index @plays =

    scope if params[:future] @plays = @plays.where(performed_at: Date.today..) end gon.as_json = @plays.as_json end def show @play = scope.find(params[:id]) end private def scope Actor.find(params[:actor_id]).plays end end ɹ࠷ॳʹ୅ೖ͢Δ΄͏͕ 
 ɹϢʔεέʔεͱͯ͠ଟͦ͏
  44.  <% @plays.each do |play| %> <% play.actors.each do |actor|

    %> <%= actor.name %> <% end %> <% end %>  <% @plays.each do |play| %> <% play.actors.each do |actor| %> <%= actor.company.name %> <% end %> <% end %> ɹ͜Μͳײ͡ͷϏϡʔ͕ 
 ɹ͋Δͱ͢Δ
  45. Case 1.  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE

    eager loading detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__3206924200111145907_11500'  class PlaysController < ApplicationController def index @plays = Play.all end end ɹجຊέʔε
  46.  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading

    detected Play => [:actors] Add to your query: .includes([:actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__3206924200111145907_11500' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__3206924200111145907_11500'  class PlaysController < ApplicationController def index @plays = Play.all.includes(:actors) end end Case 1. ɹجຊέʔε
  47. Case 2.  class PlaysController < ApplicationController def index @plays

    = Play.all end end ɹassociations ͕ 
 ɹෳ਺͋Δέʔε  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) 2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:theaters] Add to your query: .includes([:theaters])
  48.  class PlaysController < ApplicationController def index @plays = Play.all.includes([:actors,

    :theaters]) end end Case 2. ɹassociations ͕ 
 ɹෳ਺͋Δέʔε  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) 2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:theaters] Add to your query: .includes([:theaters])
  49. Case 3.  2023-10-26 14:37:36[WARN] user: makicamel GET /plays USE

    eager loading detected Play => [{:actors=>[:company]}] Add to your query: .includes([:"{:actors=>[:company]}"]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__1665884924136242781_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_plays_index_html_erb__1665884924136242781_11760'  class PlaysController < ApplicationController def index @plays = Play.all end end ɹωετ͍ͯ͠Δέʔε
  50. Case 3.  2023-10-26 14:37:36[WARN] user: makicamel GET /plays USE

    eager loading detected Play => [{:actors=>[:company]}] Add to your query: .includes([:"{:actors=>[:company]}"]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__1665884924136242781_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_plays_index_html_erb__1665884924136242781_11760'  class PlaysController < ApplicationController def index @plays = Play.all.includes([{:actors=>[:company]}]) end end ɹωετ͍ͯ͠Δέʔε
  51.  Case 4.  2023-10-26 14:57:19[WARN] user: makicamel GET /plays

    USE eager loading detected Play => [:awesome_actors] Add to your query: .includes([:awesome_actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_html_erb___4265071383862059390_11760’ /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb___4265071383862059390_11760' class PlaysController < ApplicationController def index @plays = Play.all end end ɹωετ͍ͯ͠Δ͔ͭ 
 ɹassociation ʹผ໊͕͋Δέʔε  class Play < ApplicationRecord has_many :play_actors has_many :awesome_actors, through: :play_actors, source: :actor end
  52.   class PlaysController < ApplicationController def index @plays =

    Play.all.includes({:awesome_actors=>[:company]}) end end Case 4. 2023-10-26 14:57:19[WARN] user: makicamel GET /plays USE eager loading detected Play => [:awesome_actors] Add to your query: .includes([:awesome_actors]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_html_erb___4265071383862059390_11760’ /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb___4265071383862059390_11760' ɹωετ͍ͯ͠Δ͔ͭ 
 ɹassociation ʹผ໊͕͋Δέʔε  class Play < ApplicationRecord has_many :play_actors has_many :awesome_actors, through: :play_actors, source: :actor end
  53.  Case 5.  2023-10-26 14:46:46[WARN] user: makicamel GET /plays

    USE eager loading detected Actor => [:company] Add to your query: .includes([:company]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:3:in `block (2 levels) in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__2119670124553133993_11760' class PlaysController < ApplicationController def index @plays = Play.includes(:actors) end end ɹҰ෦ includes ࡁͷέʔε
  54.  Case 5.  2023-10-26 14:46:46[WARN] user: makicamel GET /plays

    USE eager loading detected Actor => [:company] Add to your query: .includes([:company]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:3:in `block (2 levels) in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__2119670124553133993_11760' class PlaysController < ApplicationController def index @plays = Play.includes(:actors).includes({:actors=>[:company]}) end end ɹҰ෦ includes ࡁͷέʔε
  55.  AssociationsBuilder module BulletmarkRepairer class AssociationsBuilder def build(marker) return if

    marker.skip? if associations[marker.index] associations[marker.index].add(marker) else associations[marker.index] = Associations.new( marker, @application_associations, @loaded_associations ) end end end end ɹBullet ͷܯࠂΛந৅Խͨ͠ 
 ɹΠϯελϯε
  56.  AssociationsBuilder module BulletmarkRepairer class AssociationsBuilder def build(marker) return if

    marker.skip? if associations[marker.index] associations[marker.index].add(marker) else associations[marker.index] = Associations.new( marker, @application_associations, @loaded_associations ) end end end end ɹN+1 ൃੜՕॴΛΩʔʹͯ͠ 
 ɹ ɹ
  57.  AssociationsBuilder module BulletmarkRepairer class AssociationsBuilder def build(marker) return if

    marker.skip? if associations[marker.index] associations[marker.index].add(marker) else associations[marker.index] = Associations.new( marker, @application_associations, @loaded_associations ) end end end end ɹN+1 ൃੜՕॴΛΩʔʹͯ͠ 
 ɹassociation Λ૊ΈཱͯΔ
  58.  AssociationsBuilder module BulletmarkRepairer class AssociationsBuilder def build(marker) return if

    marker.skip? if associations[marker.index] associations[marker.index].add(marker) else associations[marker.index] = Associations.new( marker, @application_associations, @loaded_associations ) end end end end  2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:actors] Add to your query: .includes([:actors]) 2023-10-23 07:21:04[WARN] user: makicamel GET /plays USE eager loading detected Play => [:theaters] Add to your query: .includes([:theaters]) ɹෳ਺ͷܯࠂΛ 
 ɹͻͱͭʹ·ͱΊ͍ͨ 
 ɹέʔε͕͋Δ
  59. AssociationsBuilder •γϯϘϧΛϋογϡʹͨ͠Γ 
 [:actor]ɹˠɹ[{ actor: [:company] }] •ϋογϡͷόϦϡʔΛ഑ྻʹม׵ͯ͠஋Λ௥Ճͨ͠Γ 
 [{

    actors: :company }, :staffs] 
 ɹˠɹ[{ actors: [:company, :works] }, :staffs] •഑ྻͷ஋ͷͻͱͭͱ௥Ճ͍ͨ͠஋Λϋογϡʹม׵ͨ͠Γ 
 [{ actors: :works }]ɹˠɹ[{ actors: { works: [:staffs] } }] ͳΜ͔΋͍ͯ͠Δ ɹෳ਺ͷܯࠂΛ 
 ɹͻͱͭʹ·ͱΊ͍ͨ 
 ɹέʔε͕͋Δ
  60.   2023-10-26 14:46:46[WARN] user: makicamel GET /plays USE eager

    loading detected Actor => [:company] Add to your query: .includes([:company]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:3:in `block (2 levels) in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__2119670124553133993_11760' class PlaysController < ApplicationController def index @plays = Play.includes(:actors) end end ɹҰ෦ includes ࡁͷέʔε LoadedAssociations
  61.   2023-10-26 14:46:46[WARN] user: makicamel GET /plays USE eager

    loading detected Actor => [:company] Add to your query: .includes([:company]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:3:in `block (2 levels) in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__2119670124553133993_11760' class PlaysController < ApplicationController def index @plays = Play.includes(:actors).includes({:actors=>[:company]}) end end ɹҰ෦ includes ࡁͷέʔε LoadedAssociations
  62.   2023-10-26 14:46:46[WARN] user: makicamel GET /plays USE eager

    loading detected Actor => [:company] Add to your query: .includes([:company]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:3:in `block (2 levels) in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__2119670124553133993_11760' class PlaysController < ApplicationController def index @plays = Play.includes(:actors).includes({:actors=>[:company]}) end end ɹBullet ͷܯࠂ͔Β͸ 
 ɹ@plays ͷϕʔεͷΫϥε͕ 
 ɹԿ͔Θ͔Βͳ͍ LoadedAssociations
  63.   2023-10-26 14:46:46[WARN] user: makicamel GET /plays USE eager

    loading detected Actor => [:company] Add to your query: .includes([:company]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:3:in `block (2 levels) in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__2119670124553133993_11760' ɹBullet ͷܯࠂ͔Β͸ͲͪΒ͕ 
 ɹਖ਼͍͔͠Θ͔Βͳ͍ LoadedAssociations class PlaysController < ApplicationController def index @plays = Play.includes(:actors).includes({:actors=>[:company]}) end end .includes([:company])
  64.  LoadedAssociations module BulletmarkRepairer module ActiveRecord module QueryMethod def includes(*args)

    Thread.current[:bulletmark_repaier_loaded_associations][model.name][:includes].add(args) super(args) end def eager_load(*args) Thread.current[:bulletmark_repaier_loaded_associations][model.name][:eager_load].add(args) super(args) end def preload(*args) Thread.current[:bulletmark_repaier_loaded_associations][model.name][:preload].add(args) super(args) end end end end ɹಡΈࠐΈࡁͷ associations Λ 
 ɹϝϞ͓ͯ͘͠
  65.  LoadedAssociations ɹassociation Λ૊ΈཱͯΔ࣌ʹ 
 ɹࢀর͢Δ class Associations def initialize(marker,

    application_associations, loaded_associations) @marker = marker @application_associations = application_associations @loaded_associations = loaded_associations key = @loaded_associations.key(marker.base_class) @associations = { base: key ? { key => marker.associations } : marker.associations } end end
  66.   2023-10-26 14:46:46[WARN] user: makicamel GET /plays USE eager

    loading detected Actor => [:company] Add to your query: .includes([:company]) Call stack /Users/makicamel/awesome_app/app/views/plays/index.html.erb:3:in `block (2 levels) in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:2:in `block in _app_views_plays_index_html_erb__2119670124553133993_11760' /Users/makicamel/awesome_app/app/views/plays/index.html.erb:1:in `_app_views_plays_index_html_erb__2119670124553133993_11760' ɹactors ͸ includes ࡁͳͷͰ 
 ɹωετ͢Δͷ͕ਖ਼͍͠ 💡 LoadedAssociations class PlaysController < ApplicationController def index @plays = Play.includes(:actors).includes({:actors=>[:company]}) end end .includes([:company])
  67. •preload ͕޷·͍͠έʔε͕ଟ͍ •eager_load ͸ LEFT OUTER JOIN ͳͷͰϝϞϦΛ৯͍΍͍͢ •preload ͷ৔߹

    IN ۟ʹೖΔ id ͷ਺͕ଟ͘ʢe.g. ਺ສ݅ʣͳΔͱ 
 ύϑΥʔϚϯε͕ஶ͘͠௿Լ͢Δ͜ͱ͕͋Δ •preload or eager_load Ҏ֎ͷํ๏΋͋Δ preload vs. eager_load ઈରղ͸ͳ͍
  68. •includes ͩͱύϥϝʔλ΍ঢ়ଶʹΑΓ preload or eager_load ͕ 
 ࢖͍෼͚ΒΕΔ͜ͱ͕͋Δ •ϦΫΤετʹΑΓൃߦ SQL

    ͕มΘΔͱ෼ੳͮ͠Β͍ includes vs. preload or eager_load ෼ੳɾӡ༻໘Ͱ includes ΑΓ preload or eager_load ͕޷·͍͠
  69. ϝϞϦѹഭ͕৺഑ͳΜͰ͚͢Ͳ... N+1 → LEFT JOIN ͰϝϞϦ͕໰୊ʹͳΔͳΒ 
 ଟ͘ͷ৔߹ N+1 ͷ࣌఺Ͱ

    
 ໰୊ʹͳ͍ͬͯΔͱࢥ͍·͢Α includes vs. preload or eager_load @tricknotes
  70. •ͨ·ͨ·ݟ͚ͭͨ N+1 ΛͱΓ͋͑ͣ௚ͯ͠ PR Λ࡞Δਓ •PR ΛϤγ 👈 ͯ͠औΓࠐΉਓ N+1

    ୀ࣏൪௕ BulletmarkRepairer ͸ 
 ͜ͷ෦෼ͷ 
 αϙʔτπʔϧ 🩹
  71. •ͨ·ͨ·ݟ͚ͭͨ N+1 ΛͱΓ͋͑ͣ௚ͯ͠ PR Λ࡞Δਓ •PR ΛϤγ 👈 ͯ͠औΓࠐΉਓ •ൃߦ͞ΕΔ

    SQL ͷมԽͷ֬ೝ •࣮σʔλ্໰୊ͳͦ͞͏͔ͷ֬ೝ ͜ͷϓϩηεվળ΋ 
 ͔͚ͨͬͨ͠Ͳ 
 ؒʹ߹Θͳ͔ͬͨ ͜ͷϓϩηε΋ 
 վળ͍ͨ͠ 
 ʢؒʹ߹Θͳ͔ͬͨʣ N+1 ୀ࣏൪௕