Ruby on Rails:N+1問題とは

N+1問題は、SQLクエリを大量に発行してしまう事象のことを言う。例えば下のようなWebサイトがあるとする。Webサイトのデータは、ユーザ情報のテーブルと、ユーザIDと画像を紐づけた画像テーブルの2つから構成される。

image.png

上記のサイトにアクセスすると、下記のようなSQLクエリでデータベースからデータを取得する。下記に示したように、当該ページに表示するユーザ数の数だけ、SQLクエリが投げられている。

image.png

テーブル数が少ないうちは問題にならないけれど、レコード数が増えてくると深刻なパフォーマンス問題を引き起こすらしい。こうした挙動のことを『N+1問題』という。

原因は、データを都度取得している点にある。下記は上記Webサイトのコードで、ループ文の中で画像テーブルにアクセスする処理を逐次実行してしまっているため、SQLクエリがループの数だけ投げられる。

image.png

対処方法としては、テーブルデータをまとめて取得してしまえば、SQLクエリが大量に発行されることにはならない。よって、N+1問題には、データをまとめて引っ張ってこれるようにすることが対処法となる。方法にはいくつか方法あり、例えば次のものがある。

  • JOIN:テーブルを結合してしまう
  • Eagar Load:ある程度まとめてSELECTする

今回は、Eagar Load で対処する。

コードに includes メソッドを追加して、一度にデータを取得するようにする。

image.png

再度ブラウザにアクセスして、ログを確認する。

image.png

上記の通り、SQLをまとめて実行できるようになる。

また、ActiveStorageには、『N+1問題』に対応する方法として、標準で用意されたメソッドがある。ActiveStorage::Attachment

上のコードの @users.includes(:avatar_attachment).each の部分を @users.all.with_attached_avatar.each にすると、Eagar Load の効果を得られる。