N + 1問題を解決する


はじめに仕込んでいたgem bulletが警告ログを発したので解決してみます。

N + 1問題とは?

データベースへのアクセス回数が必要以上に多くなってしまう現象の事。モデル間のアソシエーションで発生します。

gem bulletとは?

N + 1問題が発生してる箇所を警告で教えてくれるgemです。

現状

CareRecipitent Load (0.4ms)  SELECT "care_recipitents".* FROM "care_recipitents"
  ↳ app/views/caregiver/tops/index.html.erb:1
  Caregiver Load (0.5ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  CACHE Caregiver Load (0.0ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  CACHE Caregiver Load (0.0ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  Rendered caregiver/tops/index.html.erb within layouts/caregiver (Duration: 16.2ms | Allocations: 6553)
  CACHE Caregiver Load (0.1ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

はじめに親モデルに検索がかけられ、その後に子モデルに4回クエリが発行されていることがわかります。

解決策

子モデル.includes(:親モデル)

  def index
    #@caregiver = Caregiver.find(params[:staff_member_id])
  -  @care_recipitents = CareRecipitent.all
  +  @care_recipitents = CareRecipitent.includes(:caregiver)
  end

コントローラーのindexアクションを書き換えます。

CareRecipitent Load (1.2ms)  SELECT "care_recipitents".* FROM "care_recipitents"
  ↳ app/views/caregiver/tops/index.html.erb:1
  Caregiver Load (0.6ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1  [["id", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:1
  Rendered caregiver/tops/index.html.erb within layouts/caregiver (Duration: 75.3ms | Allocations: 19989)
  Caregiver Load (0.3ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

クエリの回数が減り、無事ログを消えました。