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

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

Avatar for ANDPAD inc 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 による修正をリリースする話をします。

Avatar for ANDPAD inc

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 ୀ࣏൪௕