Rails

【Rails】パフォーマンス低下に繋がるN+1問題とは?解決策と併せて解説!

Rails

 

今回はアプリケーションのパフォーマンス低下に繋がる問題「N+1問題」について解説したいと思います。Railsを学習したての頃はなかなか理解しにくい内容かとは思いますが、開発する上でパフォーマンスは非常に重要になってくるので、ぜひ理解しておきましょう。

「N+1問題」という用語だけは知ってる人が多いかな。

N+1問題とは

N+1問題とは簡単に言うと、データベースへのアクセス回数が余計に多くなってしまう現象です。加えてこれは、モデル間のアソシエーションを利用する場合に発生します。

もう少し具体的に見ていきましょう。

例えばusersテーブルとpostsテーブルがあり、それらが1対多の関係で結びついているとします。(下図)

アソシエーションの関係

 

そしてpostsテーブルからレコードを全て取得する記述をしたとします。(以下コード)

@posts = Post.all

 

そうすると毎回user_idを変更してusersテーブルからレコードを取得してくるため、処理スピードが遅くなってしまいます。(下図)

これがN+1問題です。

クエリの発行回数
# usersテーブルから取得(1回目)
SELECT  `users`.* FROM `users` WHERE `users`.id = 1 LIMIT 1
# usersテーブルから取得(2回目)
SELECT  `users`.* FROM `users` WHERE `users`.id = 2 LIMIT 1
# usersテーブルから取得(3回目)
SELECT  `users`.* FROM `users` WHERE `users`.id = 3 LIMIT 1
この場合だとSQLのクエリ(検索文)が計4回発行されることになるね。

解決方法

このN+1問題を解決するには、includesメソッドを使用します。includesメソッドを使用することにより、必要なレコードをまとめて取得することができます。(下図)

includesメソッドを使用した時のクエリの発行回数
# usersテーブルから取得(1回のみ)
SELECT  `users`.* FROM `users` WHERE `users`.`id` IN(1, 2, 3)
この場合だとSQLのクエリ(検索文)が計2回しか発行しなくて済むね。

 

記述方法としては以下になります。

@post = Post.includes(:user)

includesメソッドの記述方法はこのようになります。「モデル名」の先頭は大文字、「紐付くモデル名」は全て小文字で記述する点に注意しましょう。

モデル名.includes(:紐付くモデル名)

RailsからDBにアクセスする際に起きていること

ここからは余談にはなりますが、知っておいて損はないので紹介したいと思います。

みなさんもご存知の通り、Railsでは例えば以下のように記載するだけでデータベースに対して命令できますよね。

@posts = Post.all

 

しかし実際は、MySQLがこの命令をSQL(データベースを操作する言語)に変換しています。もし変換しない場合だと以下のようになります。

SELECT
  *  
FROM
  posts

 

この文はターミナルでよく目にすると思います。この文をさらに噛み砕くと「*」は「レコード全て」、「posts」は「postsテーブルから」という意味になります。(下記コード)

SELECT
  * (レコード全部とってきて) 
FROM
  posts (postsテーブルから)

 

データベースとのやりとりはRubyで行なっていないので、ぜひ覚えておいてくださいね。

Ruby文でデータベースを操作できることをO/Rマッピングというよ。

まとめ

  • N+1問題とは、データベースへのアクセス回数が余計に多くなってしまう現象のこと
  • N+1問題を解決するには、includesメソッドを使用する
  • Ruby文でデータベースを操作できることをO/Rマッピングという

 

 

今回はアプリケーションのパフォーマンス低下に繋がる問題「N+1問題」について解説しました。練習用の小さなアプリケーションではあまり気にする必要はありませんが、本格的にアプリケーションを開発する際は、パフォーマンスは非常に重要になってきますので、ぜひincludesメソッドを使いこなせるように練習しておきましょう。