3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ややこしい、難しいN+1問題を解決する

Last updated at Posted at 2021-08-06

今回はincludesとかを使ってN+1問題を解決していきたいです。
さっさと解決しようかと思ったのですが、一筋縄じゃいかなかったので、今から頑張って解決しようと思います!

またこの方の記事を全面的に参考にして(パクって)記事を書いていこうと思います。

あとタイトルがクソすぎるので、いいタイトルあったら編集リクエスト待ってます。

今回のモデルのアソシエーション

drinkがいわゆる投稿です。

userはいろんな投稿をするので、

userとdrinkで1対多の関係

これだけだったら単純なのですが、

それらに加えて、imageがあります。

投稿画像とユーザー画像がそれぞれある感じ。

bulletが検知したN+1

user: root USE eager loading detected Drink => [:image_attachment] Add to your query: .includes([:image_attachment])

DrinkモデルがN+1問題を引き起こしてるっぽい。

解決していこう

drinks_controller


    @pagy,@drinks = pagy(Drink.includes(image_attachment: :blob)
                    .where.not(user_id: 6)
                    .where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: @user.id)
                    .order('drinks.created_at DESC'))

んーややこしい。。。。

色々調べてみるとこーゆー感じで書き直したらいいんじゃないかと思った。

drinks_controller


    @pagy,@drinks = pagy(Drink.includes(user: {image_attachment: :blob})
                    .where.not(user_id: 6)
                    .where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: @user.id)
                    .order('drinks.created_at DESC'))

したら今度は、

user: root USE eager loading detected Drink => [:image_attachment] Add to your query: .includes([:image_attachment])
とbulletに検知された。

userの方は解決できたっぽいけど、、、。
今度はimageの方でN+1が発生してる。。。。

この書き方でどうだ!!!(無策)


    @pagy,@drinks = pagy(Drink.includes(:user ,{image_attachment: :blob})
                    .where.not(user_id: 6)
                    .where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: @user.id)
                    .order('drinks.created_at DESC'))

解決できた。

ログを実際に見てみる


Processing by DrinksController#index as HTML
web_1              |   User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 7 LIMIT 1
web_1              |   ↳ app/helpers/sessions_helper.rb:52:in `current_user'
web_1              |    (0.8ms)  SELECT COUNT(*) FROM `drinks` WHERE `drinks`.`user_id` != 6 AND (user_id IN (SELECT followed_id FROM relationships WHERE follower_id = 7) OR user_id = 7)
web_1              |   ↳ app/controllers/drinks_controller.rb:14:in `index'
web_1              |   Rendering layout layouts/application.html.erb
web_1              |   Rendering drinks/index.html.erb within layouts/application
web_1              |   Drink Load (1.1ms)  SELECT `drinks`.* FROM `drinks` WHERE `drinks`.`user_id` != 6 AND (user_id IN (SELECT followed_id FROM relationships WHERE follower_id = 7) OR user_id = 7) ORDER BY drinks.created_at DESC LIMIT 2 OFFSET 0
web_1              |   ↳ app/views/drinks/_drinks_index.html.erb:12
web_1              |   User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 7
web_1              |   ↳ app/views/drinks/_drinks_index.html.erb:12
web_1              |   ActiveStorage::Attachment Load (0.7ms)  SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_type` = 'Drink' AND `active_storage_attachments`.`name` = 'image' AND `active_storage_attachments`.`record_id` IN (8, 7)
web_1              |   ↳ app/views/drinks/_drinks_index.html.erb:12
web_1              |   ActiveStorage::Blob Load (0.8ms)  SELECT `active_storage_blobs`.* FROM `active_storage_blobs` WHERE `active_storage_blobs`.`id` IN (12, 13)
web_1              |   ↳ app/views/drinks/_drinks_index.html.erb:12
web_1              |   ActiveStorage::Attachment Load (0.6ms)  SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 7 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'image' LIMIT 1
web_1              |   ↳ app/views/drinks/_drinks_index.html.erb:18
web_1              |   Like Load (0.7ms)  SELECT `likes`.* FROM `likes` WHERE `likes`.`user_id` = 7 AND `likes`.`drink_id` = 8 LIMIT 1
web_1              |   ↳ app/views/likes/_like.html.erb:2
web_1              |    (0.6ms)  SELECT COUNT(*) FROM `likes` WHERE `likes`.`drink_id` = 8
web_1              |   ↳ app/views/likes/_like.html.erb:6
web_1              |   Rendered likes/_like.html.erb (Duration: 9.1ms | Allocations: 1639)
web_1              |   Like Load (0.7ms)  SELECT `likes`.* FROM `likes` WHERE `likes`.`user_id` = 7 AND `likes`.`drink_id` = 7 LIMIT 1
web_1              |   ↳ app/views/likes/_like.html.erb:2
web_1              |    (0.7ms)  SELECT COUNT(*) FROM `likes` WHERE `likes`.`drink_id` = 7
web_1              |   ↳ app/views/likes/_like.html.erb:6
web_1              |   Rendered likes/_like.html.erb (Duration: 10.2ms | Allocations: 1558)
web_1              |   Rendered drinks/_drinks_index.html.erb (Duration: 56.6ms | Allocations: 12908)
web_1              |   Rendered drinks/index.html.erb within layouts/application (Duration: 57.9ms | Allocations: 12979)
web_1              | [Webpacker] Everything's up-to-date. Nothing to do

# なぜこの書き方で上手くいったのかの僕の予想

@drinks = pagy(Drink.includes(:user ,{image_attachment: :blob})

って書き方は、
drinkとアソシエーション組んでるuserとimageをどっちもincludesメソッドの引数に指定したから上手くいったのでしょう。

一方失敗した下のような書き方は

@drinks = pagy(Drink.includes(user: {image_attachment: :blob})

userモデルとアソシエーションを組んでるimegeをN+1問題を起こさないようにしてーー。

って意味のコードになってしまったのでしょう。。。。きっと。。。

3
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?