そのpreload、本当に必要ですか？〜遅延ロード活用〜

まずは下記のコードを見てください。

```ruby
review = Review.preload(:user, :book).find_by(id: review_id)
```

このようなコードを見かけたとき、あなたはどうしますか？
私なら`preload`は付けなくて良いよ。と指摘すると思います。

この記事では、なぜこの`preload`は不要なのか説明したいと思います。

# preloadとは
preloadをつけると指定した関連データを同時に取得することができます。
この例の場合、reviewを取得したときに関連するuserとbookも同時に取得します。

下記にirbで実行した結果を載せておきます。
reviewを取得したときにuserとbookもSELECTしており、実際に使うところではSQLが発行されていないことがわかります。

```irb
irb(main):011:0> review_id = 15
=> 15
irb(main):012:0> review = Review.preload(:user, :book).find_by(id: review_id)
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 15 LIMIT 1
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
  Book Load (0.8ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1
=> #<Review id: 15, content: "hogehoge", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-15 14:21:23", updated_at: "2020-06-15 14:21:23">
irb(main):013:0> review.user
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):014:0> review.book
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">
```

# preloadはどういうときに使うのか？
主にN+1の対策で使われます。
N+1についてはここでは詳しくは述べませんが、下記のようにループなどで関連データの取得SQLが１件ずつ発行されるような事象のことです。

```irb
irb(main):022:0> Review.all.each do |review|
irb(main):023:1*   review.book
irb(main):024:1> end
  Review Load (0.6ms)  SELECT `reviews`.* FROM `reviews`
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 2 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 3 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 4 LIMIT 1
  Book Load (2.7ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 5 LIMIT 1
```

上記では、bookを事前に取得していないのでreview.bookのところで１件ずつSQLを発行しています。
prealodをつけて事前にbookを取得しておくと下記のようになります。

```irb
irb(main):025:0> Review.all.preload(:book).each do |review|
irb(main):026:1*   review.book
irb(main):027:1> end
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews`
  Book Load (0.7ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` IN (1, 2, 3, 4, 5)
```

ループに入る前にReview.allで取得できたreviewに関連するbookを1つのSQLで取得していることがわかります。
ループ前にまとめで取得できているのでループ中にはSQLが発行されません。
一般的にSQL発行はコストがかかる処理なので、SQLが１回になることでパフォーマンスが向上します。
上記例でもSQLの合計実行時間をみるとパフォーマンスに差が出ていることがわかります。

# なぜ今回は付けなくて良いのか？
では、最初の例の場合はどうでしょうか？
reviewを1件しか取得していないので先ほどのようにループでN+1になることはありえません。

preloadをしているということは少なくとものちに使う可能性があるということだと思います。
次の例を見てみましょう。

```ruby
# userを取得
# あとでuserとreviewを使うのでpreloadしておく
review = Review.preload(:user, :book).find_by(id: review_id)

# userを使う
review.user

# bookを使う
review.book
```

preloadをつけているので、reviewを取得したときにuserやbookも取得されます。
実行結果は下記の通り。

```irb
irb(main):007:0> review = Review.preload(:user, :book).find_by(id: review_id)
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 36 LIMIT 1
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1
=> #<Review id: 36, content: "", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-30 15:20:01", updated_at: "2020-06-30 15:20:01">
irb(main):008:0> review.user
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):009:0> review.book
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">
```

では、もしpreloadをつけていなかったらどうなるでしょうか？

```irb
irb(main):010:0> review = Review.find_by(id: review_id)
  Review Load (0.7ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 36 LIMIT 1
=> #<Review id: 36, content: "", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-30 15:20:01", updated_at: "2020-06-30 15:20:01">
irb(main):011:0> review.user
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):012:0> review.book
  Book Load (0.6ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">
```

reviewを取得したときにはuserとbookは取得されず、使っているところでSQLが発行されています。
ただ、reviewが一件しかないので発行されているSQLの数は一緒です。
この例の場合だと、preloadをつけてもつけなくても効率は同じですね

では、次の例ではどうでしょうか？

```ruby
# userを取得
# あとでuserとreviewを使うのでpreloadしておく
review = Review.preload(:user, :book).find_by(id: review_id)

# ある条件の時はuserを使う
if hoge
  review.user
end

# ある条件の時はbookを使う
if fuga
  review.book
end
```

preloadをつけているので、reviewを取得したときにuserやbookも取得されます。
hogeやfugaがtrueの場合は、userもreviewも使うのでpreloadをしていてもしていなくてもSQLの数は一緒です。

では、falseの場合はどうでしょうか？
例えばhogeがfalseの場合はuserは使わないので、preloadで取得したuserを使うことはありません。
fugaがfalseの場合も同様にbookを使うことはありません。

今回はもしpreloadをつけていなかったらどうなるでしょうか？

```ruby
# userを取得
review = Review.find_by(id: review_id)

# ある条件の時はuserを使う
if hoge
  review.user
end

# ある条件の時はbookを使う
if fuga
  review.book
end
```

reviewを取得したときはuserやbookは取得されません。
hogeやfugaがtrueの場合は使用する箇所でreviewやbookが取得されます。
もしfalseの場合は取得されません。

こちらの実装の場合は使用するときのみ取得することができます。
ちなみに、このように必要になったときにデータを取得する実装は遅延ロードと呼ばれています。

どちらの方が効率が良いかおわかりいただけたでしょうか？
1件のモデルに対してpreloadをした場合、preloadで取得したモデルを全部使った場合でもpreloadをつけていない場合とSQLの数は同じです。
もし１つでも条件によって使わないパターンがある場合はSQLの数が多くなります。

最初の例のように1件だけ取得する場合はpreloadをしても意味がなく、むしろ非効率になるので注意が必要です。

# 最後に

Railsを覚えたばかりの方などなんとなくpreloadやeager_loadを知っている場合、とりあえずつけておけばいいんでしょ？
と思っている方も多いと思います。

レビュアーとしてもN+1を指摘する人は多いけど、今回のような無駄なpreloadを指摘する人は少ないと感じています（個人の感想です）

N+1を倒してくれるpreloadやeager_loadはつけておいて悪いことはないと思われがちですが、今回のように非効率になってしまうパターンもあるので意識していなかった方は意識しておきましょう。